From 4e42229bdd980ab50da8b83b1aa1b0877b5a9ef6 Mon Sep 17 00:00:00 2001 From: Simone Date: Mon, 22 Jan 2024 08:51:55 +0100 Subject: [PATCH] Squashed 'external/gainput/' content from commit 2be0a50 git-subtree-dir: external/gainput git-subtree-split: 2be0a50089eafcc6fccb66142180082e48f27f4c --- .appveyor.yml | 15 + .gitignore | 22 + .travis.yml | 10 + CMakeLists.txt | 35 + Doxyfile | 1808 ++++ LICENSE | 7 + README.md | 115 + build.gradle | 64 + extern/android/AndroidManifest.xml | 24 + .../android/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4208 bytes .../android/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2555 bytes .../android/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6114 bytes .../android/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10056 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 14696 bytes extern/android/res/values/colors.xml | 6 + extern/android/res/values/strings.xml | 3 + extern/android/res/values/styles.xml | 11 + extern/catch/catch.hpp | 9427 +++++++++++++++++ extern/cmake/AndroidNdkModules.cmake | 59 + extern/cmake/android.toolchain.cmake | 1688 +++ extern/cmake/iOS.cmake | 200 + extern/ios/Info.plist | 49 + extern/ios/Launch Screen.storyboard | 53 + lib/CMakeLists.txt | 79 + lib/include/gainput/GainputAllocator.h | 228 + lib/include/gainput/GainputContainers.h | 508 + lib/include/gainput/GainputDebugRenderer.h | 34 + lib/include/gainput/GainputHelpers.h | 71 + lib/include/gainput/GainputInputDeltaState.h | 59 + lib/include/gainput/GainputInputDevice.h | 271 + .../gainput/GainputInputDeviceBuiltIn.h | 68 + .../gainput/GainputInputDeviceKeyboard.h | 267 + lib/include/gainput/GainputInputDeviceMouse.h | 101 + lib/include/gainput/GainputInputDevicePad.h | 163 + lib/include/gainput/GainputInputDeviceTouch.h | 107 + lib/include/gainput/GainputInputListener.h | 77 + lib/include/gainput/GainputInputManager.h | 331 + lib/include/gainput/GainputInputMap.h | 195 + lib/include/gainput/GainputInputState.h | 60 + lib/include/gainput/GainputIos.h | 26 + lib/include/gainput/GainputLog.h | 39 + lib/include/gainput/GainputMapFilters.h | 17 + lib/include/gainput/gainput.h | 193 + .../gestures/GainputButtonStickGesture.h | 47 + .../gestures/GainputDoubleClickGesture.h | 99 + .../gainput/gestures/GainputGestures.h | 69 + .../gainput/gestures/GainputHoldGesture.h | 94 + .../gainput/gestures/GainputPinchGesture.h | 86 + .../gainput/gestures/GainputRotateGesture.h | 87 + .../GainputSimultaneouslyDownGesture.h | 66 + .../gainput/gestures/GainputTapGesture.h | 66 + .../gainput/recorder/GainputInputPlayer.h | 65 + .../gainput/recorder/GainputInputRecorder.h | 73 + .../gainput/recorder/GainputInputRecording.h | 125 + .../gainput/gainput/BasicActivity.java | 85 + .../de/johanneskuhlmann/gainput/Gainput.java | 385 + lib/source/gainput/GainputAllocator.cpp | 58 + lib/source/gainput/GainputHelpersEvdev.h | 126 + lib/source/gainput/GainputInputDeltaState.cpp | 75 + lib/source/gainput/GainputInputDevice.cpp | 97 + lib/source/gainput/GainputInputManager.cpp | 617 ++ lib/source/gainput/GainputInputMap.cpp | 572 + lib/source/gainput/GainputInputState.cpp | 32 + lib/source/gainput/GainputIos.mm | 185 + lib/source/gainput/GainputMac.mm | 18 + lib/source/gainput/GainputMapFilters.cpp | 20 + lib/source/gainput/GainputWindows.h | 17 + .../builtin/GainputInputDeviceBuiltIn.cpp | 145 + .../GainputInputDeviceBuiltInAndroid.h | 203 + .../builtin/GainputInputDeviceBuiltInImpl.h | 21 + .../builtin/GainputInputDeviceBuiltInIos.h | 52 + .../builtin/GainputInputDeviceBuiltInIos.mm | 82 + .../builtin/GainputInputDeviceBuiltInNull.h | 38 + lib/source/gainput/dev/GainputDev.cpp | 717 ++ lib/source/gainput/dev/GainputDev.h | 52 + lib/source/gainput/dev/GainputDevProtocol.h | 28 + .../gainput/dev/GainputMemoryStream.cpp | 91 + lib/source/gainput/dev/GainputMemoryStream.h | 49 + lib/source/gainput/dev/GainputNetAddress.cpp | 44 + lib/source/gainput/dev/GainputNetAddress.h | 53 + .../gainput/dev/GainputNetConnection.cpp | 261 + lib/source/gainput/dev/GainputNetConnection.h | 47 + lib/source/gainput/dev/GainputNetListener.cpp | 185 + lib/source/gainput/dev/GainputNetListener.h | 38 + lib/source/gainput/dev/GainputStream.h | 145 + lib/source/gainput/gainput.cpp | 383 + .../gestures/GainputButtonStickGesture.cpp | 74 + .../gestures/GainputDoubleClickGesture.cpp | 124 + .../gainput/gestures/GainputHoldGesture.cpp | 134 + .../gainput/gestures/GainputPinchGesture.cpp | 120 + .../gainput/gestures/GainputRotateGesture.cpp | 126 + .../GainputSimultaneouslyDownGesture.cpp | 63 + .../gainput/gestures/GainputTapGesture.cpp | 71 + .../keyboard/GainputInputDeviceKeyboard.cpp | 191 + .../GainputInputDeviceKeyboardAndroid.h | 206 + .../GainputInputDeviceKeyboardEvdev.h | 243 + .../keyboard/GainputInputDeviceKeyboardImpl.h | 25 + .../GainputInputDeviceKeyboardLinux.h | 255 + .../GainputInputDeviceKeyboardMac.cpp | 271 + .../keyboard/GainputInputDeviceKeyboardMac.h | 58 + .../keyboard/GainputInputDeviceKeyboardNull.h | 34 + .../keyboard/GainputInputDeviceKeyboardWin.h | 277 + .../GainputInputDeviceKeyboardWinRaw.h | 259 + .../keyboard/GainputKeyboardKeyNames.h | 184 + .../gainput/mouse/GainputInputDeviceMouse.cpp | 149 + .../mouse/GainputInputDeviceMouseEvdev.h | 161 + .../mouse/GainputInputDeviceMouseImpl.h | 20 + .../mouse/GainputInputDeviceMouseLinux.h | 122 + .../mouse/GainputInputDeviceMouseMac.h | 36 + .../mouse/GainputInputDeviceMouseMac.mm | 166 + .../mouse/GainputInputDeviceMouseNull.h | 28 + .../mouse/GainputInputDeviceMouseWin.h | 135 + .../mouse/GainputInputDeviceMouseWinRaw.h | 179 + lib/source/gainput/mouse/GainputMouseInfo.h | 48 + .../gainput/pad/GainputInputDevicePad.cpp | 274 + .../pad/GainputInputDevicePadAndroid.h | 79 + .../gainput/pad/GainputInputDevicePadImpl.h | 23 + .../gainput/pad/GainputInputDevicePadIos.h | 60 + .../gainput/pad/GainputInputDevicePadIos.mm | 364 + .../gainput/pad/GainputInputDevicePadLinux.h | 285 + .../gainput/pad/GainputInputDevicePadMac.cpp | 413 + .../gainput/pad/GainputInputDevicePadMac.h | 57 + .../gainput/pad/GainputInputDevicePadNull.h | 43 + .../gainput/pad/GainputInputDevicePadWin.h | 165 + .../gainput/recorder/GainputInputPlayer.cpp | 111 + .../gainput/recorder/GainputInputRecorder.cpp | 146 + .../recorder/GainputInputRecording.cpp | 179 + .../gainput/touch/GainputInputDeviceTouch.cpp | 157 + .../touch/GainputInputDeviceTouchAndroid.h | 99 + .../touch/GainputInputDeviceTouchImpl.h | 22 + .../touch/GainputInputDeviceTouchIos.h | 149 + .../touch/GainputInputDeviceTouchNull.h | 30 + lib/source/gainput/touch/GainputTouchInfo.h | 59 + samples/CMakeLists.txt | 20 + samples/android/AndroidManifest.xml | 34 + samples/android/project.properties | 14 + samples/android/res/values/strings.xml | 4 + samples/basic/CMakeLists.txt | 37 + samples/basic/basicsample_android.cpp | 106 + samples/basic/basicsample_android_generic.cpp | 77 + samples/basic/basicsample_ios.mm | 114 + samples/basic/basicsample_linux.cpp | 119 + samples/basic/basicsample_mac.mm | 106 + samples/basic/basicsample_win.cpp | 159 + samples/dynamic/CMakeLists.txt | 25 + samples/dynamic/dynamicsample.cpp | 216 + samples/gesture/CMakeLists.txt | 25 + samples/gesture/gesturesample.cpp | 294 + samples/listener/CMakeLists.txt | 24 + samples/listener/listenersample.cpp | 175 + samples/recording/CMakeLists.txt | 24 + samples/recording/recordingsample.cpp | 161 + samples/samplefw/SampleFramework.cpp | 359 + samples/samplefw/SampleFramework.h | 48 + samples/sync/CMakeLists.txt | 24 + samples/sync/syncsample.cpp | 120 + test/CMakeLists.txt | 37 + test/test.cpp | 17 + test/test_inputdevice.cpp | 184 + test/test_inputmanager.cpp | 79 + test/test_inputmap.cpp | 177 + test/test_inputrecording.cpp | 133 + test/test_inputstate.cpp | 48 + tools/html5client/index.html | 40 + tools/html5client/touch.html | 159 + 170 files changed, 31921 insertions(+) create mode 100644 .appveyor.yml create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CMakeLists.txt create mode 100644 Doxyfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 extern/android/AndroidManifest.xml create mode 100644 extern/android/res/mipmap-hdpi/ic_launcher.png create mode 100644 extern/android/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 extern/android/res/mipmap-mdpi/ic_launcher.png create mode 100644 extern/android/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 extern/android/res/mipmap-xhdpi/ic_launcher.png create mode 100644 extern/android/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 extern/android/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 extern/android/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 extern/android/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 extern/android/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 extern/android/res/values/colors.xml create mode 100644 extern/android/res/values/strings.xml create mode 100644 extern/android/res/values/styles.xml create mode 100644 extern/catch/catch.hpp create mode 100644 extern/cmake/AndroidNdkModules.cmake create mode 100644 extern/cmake/android.toolchain.cmake create mode 100644 extern/cmake/iOS.cmake create mode 100644 extern/ios/Info.plist create mode 100644 extern/ios/Launch Screen.storyboard create mode 100644 lib/CMakeLists.txt create mode 100644 lib/include/gainput/GainputAllocator.h create mode 100644 lib/include/gainput/GainputContainers.h create mode 100644 lib/include/gainput/GainputDebugRenderer.h create mode 100644 lib/include/gainput/GainputHelpers.h create mode 100644 lib/include/gainput/GainputInputDeltaState.h create mode 100644 lib/include/gainput/GainputInputDevice.h create mode 100644 lib/include/gainput/GainputInputDeviceBuiltIn.h create mode 100644 lib/include/gainput/GainputInputDeviceKeyboard.h create mode 100644 lib/include/gainput/GainputInputDeviceMouse.h create mode 100644 lib/include/gainput/GainputInputDevicePad.h create mode 100644 lib/include/gainput/GainputInputDeviceTouch.h create mode 100644 lib/include/gainput/GainputInputListener.h create mode 100644 lib/include/gainput/GainputInputManager.h create mode 100644 lib/include/gainput/GainputInputMap.h create mode 100644 lib/include/gainput/GainputInputState.h create mode 100644 lib/include/gainput/GainputIos.h create mode 100644 lib/include/gainput/GainputLog.h create mode 100644 lib/include/gainput/GainputMapFilters.h create mode 100644 lib/include/gainput/gainput.h create mode 100644 lib/include/gainput/gestures/GainputButtonStickGesture.h create mode 100644 lib/include/gainput/gestures/GainputDoubleClickGesture.h create mode 100644 lib/include/gainput/gestures/GainputGestures.h create mode 100644 lib/include/gainput/gestures/GainputHoldGesture.h create mode 100644 lib/include/gainput/gestures/GainputPinchGesture.h create mode 100644 lib/include/gainput/gestures/GainputRotateGesture.h create mode 100644 lib/include/gainput/gestures/GainputSimultaneouslyDownGesture.h create mode 100644 lib/include/gainput/gestures/GainputTapGesture.h create mode 100644 lib/include/gainput/recorder/GainputInputPlayer.h create mode 100644 lib/include/gainput/recorder/GainputInputRecorder.h create mode 100644 lib/include/gainput/recorder/GainputInputRecording.h create mode 100644 lib/java/com/example/gainput/gainput/BasicActivity.java create mode 100644 lib/java/de/johanneskuhlmann/gainput/Gainput.java create mode 100644 lib/source/gainput/GainputAllocator.cpp create mode 100644 lib/source/gainput/GainputHelpersEvdev.h create mode 100644 lib/source/gainput/GainputInputDeltaState.cpp create mode 100644 lib/source/gainput/GainputInputDevice.cpp create mode 100644 lib/source/gainput/GainputInputManager.cpp create mode 100644 lib/source/gainput/GainputInputMap.cpp create mode 100644 lib/source/gainput/GainputInputState.cpp create mode 100644 lib/source/gainput/GainputIos.mm create mode 100644 lib/source/gainput/GainputMac.mm create mode 100644 lib/source/gainput/GainputMapFilters.cpp create mode 100644 lib/source/gainput/GainputWindows.h create mode 100644 lib/source/gainput/builtin/GainputInputDeviceBuiltIn.cpp create mode 100644 lib/source/gainput/builtin/GainputInputDeviceBuiltInAndroid.h create mode 100644 lib/source/gainput/builtin/GainputInputDeviceBuiltInImpl.h create mode 100644 lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.h create mode 100644 lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.mm create mode 100644 lib/source/gainput/builtin/GainputInputDeviceBuiltInNull.h create mode 100644 lib/source/gainput/dev/GainputDev.cpp create mode 100644 lib/source/gainput/dev/GainputDev.h create mode 100644 lib/source/gainput/dev/GainputDevProtocol.h create mode 100644 lib/source/gainput/dev/GainputMemoryStream.cpp create mode 100644 lib/source/gainput/dev/GainputMemoryStream.h create mode 100644 lib/source/gainput/dev/GainputNetAddress.cpp create mode 100644 lib/source/gainput/dev/GainputNetAddress.h create mode 100644 lib/source/gainput/dev/GainputNetConnection.cpp create mode 100644 lib/source/gainput/dev/GainputNetConnection.h create mode 100644 lib/source/gainput/dev/GainputNetListener.cpp create mode 100644 lib/source/gainput/dev/GainputNetListener.h create mode 100644 lib/source/gainput/dev/GainputStream.h create mode 100644 lib/source/gainput/gainput.cpp create mode 100644 lib/source/gainput/gestures/GainputButtonStickGesture.cpp create mode 100644 lib/source/gainput/gestures/GainputDoubleClickGesture.cpp create mode 100644 lib/source/gainput/gestures/GainputHoldGesture.cpp create mode 100644 lib/source/gainput/gestures/GainputPinchGesture.cpp create mode 100644 lib/source/gainput/gestures/GainputRotateGesture.cpp create mode 100644 lib/source/gainput/gestures/GainputSimultaneouslyDownGesture.cpp create mode 100644 lib/source/gainput/gestures/GainputTapGesture.cpp create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboard.cpp create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardAndroid.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardEvdev.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardImpl.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardLinux.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.cpp create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardNull.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardWin.h create mode 100644 lib/source/gainput/keyboard/GainputInputDeviceKeyboardWinRaw.h create mode 100644 lib/source/gainput/keyboard/GainputKeyboardKeyNames.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouse.cpp create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseEvdev.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseImpl.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseLinux.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseMac.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseMac.mm create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseNull.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseWin.h create mode 100644 lib/source/gainput/mouse/GainputInputDeviceMouseWinRaw.h create mode 100644 lib/source/gainput/mouse/GainputMouseInfo.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePad.cpp create mode 100644 lib/source/gainput/pad/GainputInputDevicePadAndroid.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePadImpl.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePadIos.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePadIos.mm create mode 100644 lib/source/gainput/pad/GainputInputDevicePadLinux.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePadMac.cpp create mode 100644 lib/source/gainput/pad/GainputInputDevicePadMac.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePadNull.h create mode 100644 lib/source/gainput/pad/GainputInputDevicePadWin.h create mode 100644 lib/source/gainput/recorder/GainputInputPlayer.cpp create mode 100644 lib/source/gainput/recorder/GainputInputRecorder.cpp create mode 100644 lib/source/gainput/recorder/GainputInputRecording.cpp create mode 100644 lib/source/gainput/touch/GainputInputDeviceTouch.cpp create mode 100644 lib/source/gainput/touch/GainputInputDeviceTouchAndroid.h create mode 100644 lib/source/gainput/touch/GainputInputDeviceTouchImpl.h create mode 100644 lib/source/gainput/touch/GainputInputDeviceTouchIos.h create mode 100644 lib/source/gainput/touch/GainputInputDeviceTouchNull.h create mode 100644 lib/source/gainput/touch/GainputTouchInfo.h create mode 100644 samples/CMakeLists.txt create mode 100644 samples/android/AndroidManifest.xml create mode 100644 samples/android/project.properties create mode 100644 samples/android/res/values/strings.xml create mode 100644 samples/basic/CMakeLists.txt create mode 100644 samples/basic/basicsample_android.cpp create mode 100644 samples/basic/basicsample_android_generic.cpp create mode 100644 samples/basic/basicsample_ios.mm create mode 100644 samples/basic/basicsample_linux.cpp create mode 100644 samples/basic/basicsample_mac.mm create mode 100644 samples/basic/basicsample_win.cpp create mode 100644 samples/dynamic/CMakeLists.txt create mode 100644 samples/dynamic/dynamicsample.cpp create mode 100644 samples/gesture/CMakeLists.txt create mode 100644 samples/gesture/gesturesample.cpp create mode 100644 samples/listener/CMakeLists.txt create mode 100644 samples/listener/listenersample.cpp create mode 100644 samples/recording/CMakeLists.txt create mode 100644 samples/recording/recordingsample.cpp create mode 100644 samples/samplefw/SampleFramework.cpp create mode 100644 samples/samplefw/SampleFramework.h create mode 100644 samples/sync/CMakeLists.txt create mode 100644 samples/sync/syncsample.cpp create mode 100644 test/CMakeLists.txt create mode 100644 test/test.cpp create mode 100644 test/test_inputdevice.cpp create mode 100644 test/test_inputmanager.cpp create mode 100644 test/test_inputmap.cpp create mode 100644 test/test_inputrecording.cpp create mode 100644 test/test_inputstate.cpp create mode 100644 tools/html5client/index.html create mode 100644 tools/html5client/touch.html diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..335c15fe --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,15 @@ + +platform: + - x64 + +configuration: + - Debug + - Release + +before_build: + - md build + - cd build + - cmake -G "Visual Studio 14 2015 Win64" .. + +build: + project: build\Project.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..823ce37b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +*.[doa] +*.swp +*.mk +*.pyc +.lock-* +.cproject +.project +build/ +__pycache__/ +.depproj/ +gainput_2008.* +docs/ +gainput.sdf +gainput.sln +gainput.unsuccessfulbuild +*.opensdf +cmakebuild*/ + +.idea +.gradle +.externalNativeBuild +gradle diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..e2758a80 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +os: + - linux + - osx +language: cpp +compiler: + - gcc + - clang +sudo: false +script: mkdir build_debug && cd build_debug/ && cmake -DCMAKE_BUILD_TYPE=Debug .. && make && test/gainputtest && cd .. && mkdir build_release && cd build_release/ && cmake -DCMAKE_BUILD_TYPE=Release .. && make && test/gainputtest + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..a443b66b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 2.8) +set(GAINPUT_MAJOR_VERSION 1) +set(GAINPUT_MINOR_VERSION 0) +set(GAINPUT_PATCH_VERSION 0) +set(GAINPUT_VERSION ${GAINPUT_MAJOR_VERSION}.${GAINPUT_MINOR_VERSION}.${GAINPUT_PATCH_VERSION}) + +option(GAINPUT_SAMPLES "Build Samples for Gainput" ON) +option(GAINPUT_TESTS "Build Tests for Gainput" ON) +option(GAINPUT_BUILD_SHARED "BUILD_SHARED" ON) +option(GAINPUT_BUILD_STATIC "BUILD_STATIC" ON) + +if(!WIN32) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Wextra") +else() + set(XINPUT "Xinput9_1_0") + if ( ${CMAKE_SYSTEM_VERSION} LESS 6.1 ) + set(XINPUT, "xinput") + endif() +endif() + +if(ANDROID) + include(extern/cmake/AndroidNdkModules.cmake) + android_ndk_import_module_native_app_glue() +endif() + +add_subdirectory(lib) + +if(GAINPUT_SAMPLES) + add_subdirectory(samples) +endif() + +if(GAINPUT_TESTS) + add_subdirectory(test) +endif() + diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..2921c392 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,1808 @@ +# Doxyfile 1.8.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Gainput" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = "v1.0.0" + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs/ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. Note that you specify absolute paths here, but also +# relative paths, which will be relative from the directory where doxygen is +# started. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, +# and language is one of the parsers supported by doxygen: IDL, Java, +# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, +# C++. For instance to make doxygen treat .inc files as Fortran files (default +# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note +# that for custom extensions you also need to set FILE_PATTERNS otherwise the +# files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented classes, +# or namespaces to their corresponding documentation. Such a link can be +# prevented in individual cases by by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter and setter methods for a property. Setting this option to YES (the default) will make doxygen replace the get and set methods by a property in the documentation. This will only work if the methods are indeed getting or setting a simple type. If this is not the case, or you want to show the methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal +# scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = lib/include/ lib/source/gainput/gainput.cpp + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = ./ + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C, C++ and Fortran comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If left blank doxygen will +# generate a default style sheet. Note that it is recommended to use +# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this +# tag will in the future become obsolete. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional +# user-defined cascading style sheet that is included after the standard +# style sheets created by doxygen. Using this option one can overrule +# certain style aspects. This is preferred over using HTML_STYLESHEET +# since it does not replace the standard style sheet and is therefor more +# robust against future updates. Doxygen will copy the style sheet file to +# the output directory. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely +# identify the documentation publisher. This should be a reverse domain-name +# style string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = GAINPUT_ENABLE_ALL_GESTURES GAINPUT_PLATFORM_LINUX GAINPUT_PLATFORM_WIN GAINPUT_PLATFORM_ANDROID GAINPUT_ENABLE_DOUBLE_CLICK_GESTURE GAINPUT_ENABLE_HOLD_GESTURE GAINPUT_ENABLE_PINCH_GESTURE GAINPUT_ENABLE_ROTATE_GESTURE GAINPUT_ENABLE_SIMULTANEOUSLY_DOWN_GESTURE GAINPUT_ENABLE_TAP_GESTURE GAINPUT_ENABLE_RECORDER + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cabf9f97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2013-2017 Johannes Kuhlmann + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..e4492103 --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +This project is archived. It's neither maintained nor developed anymore. +======= + +Gainput [![Build Status](https://travis-ci.org/jkuhlmann/gainput.png?branch=master)](https://travis-ci.org/jkuhlmann/gainput) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) +======= + +Gainput is the awesome C++ input library for your game: + +- handles your input needs from low-level device reading to high-level mapping of user-defined buttons +- well-documented, clean, lightweight, and easy to use +- a unified interface on all supported platforms: **Android NDK, iOS/tvOS, Linux, macOS, Windows** +- supported devices: keyboard, mouse, gamepad, multi-touch, device built-in sensors +- [Open Source (MIT license)](https://github.com/jkuhlmann/gainput/blob/master/LICENSE) +- [complete list of features](#features) +- [API documentation](http://gainput.johanneskuhlmann.de/api/) + + +Usage +----- + +```cpp +#include + +enum Button +{ + ButtonConfirm +}; + +gainput::InputManager manager; +manager.SetDisplaySize(displayWidth, displayHeight); +const gainput::DeviceId keyboardId = manager.CreateDevice(); +const gainput::DeviceId mouseId = manager.CreateDevice(); +const gainput::DeviceId padId = manager.CreateDevice(); +const gainput::DeviceId touchId = manager.CreateDevice(); + +gainput::InputMap map(manager); +map.MapBool(ButtonConfirm, keyboardId, gainput::KeyReturn); +map.MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); +map.MapBool(ButtonConfirm, padId, gainput::PadButtonA); +map.MapBool(ButtonConfirm, touchId, gainput::Touch0Down); + +while (running) +{ + manager.Update(); + + // May need some platform-specific message handling here + + if (map.GetBoolWasDown(ButtonConfirm)) + { + // Confirmed! + } +} +``` + + +Features +-------- + +- Offers a **unified interface** on all supported platforms. (Some minor changes are necessary to setup the library.) +- Provides a low-level and high-level interface: Query the state of input devices buttons directly or map device buttons to a user button. That way it's easy to support alternative inputs or change the **input mappings** around later. +- Supports **recording and playback** of input sequences. +- Features a **network server** to obtain information on devices and mappings from. +- Two Gainput instances can **sync device states over the network**. It's also possible to receive **multi-touch inputs from a smartphone**'s regular browser. +- Completely written in portable **C++**. +- **No STL** is used. **No exceptions** are thrown. **No RTTI** is used. **No C++11**, and **no boost**. +- **No weird external dependencies** are used. Relies on the existing platform SDKs. +- **Easily set up and built** using your favorite IDE/build tool. +- **Listeners** can be installed both for devices buttons as well as user buttons. That way you are notified when a button state changes. +- **Gestures** allow for more complex input patterns to be detected, for example double-clicking, pinch/rotation gestures, or holding several buttons simultaneously. +- An **external allocator** can be supplied to the library so that all memory management is done the way you want it. +- Supports **raw input** on Linux and Windows. +- Gamepad rumbling is supported where available. +- It's easy to check for all pressed buttons so that offering a way to the players to remap their buttons is easy to implement. Similarly it's easy to save and load mappings. +- Possibly unnecessary features, like gestures or the network server, are easily disabled. +- **Dead zones** can be set up for any float-value button. +- **State changes**, i.e. if a button is newly down or just released, can be checked for. + + +Building +-------- + +By default, Gainput is built using [CMake](http://www.cmake.org/). + +1. Run `mkdir build` +1. Run `cmake ..` +1. Run `make` +1. The library can be found in `lib/`, the executables in `samples/`. + + +Contributing +------------ + +Everyone is welcome to contribute to the library. If you find any problems, you can submit them using [GitHub's issue system](https://github.com/jkuhlmann/gainput/issues). If you want to contribute code, you should fork the project and then send a pull request. + + +Dependencies +------------ + +Gainput has a minimal number of external dependencies to make it as self-contained as possible. It uses the platforms' default ways of getting inputs and doesn't use the STL. + + +Testing +------- + +Generally, testing should be done by building and running Gainput on all supported platforms. The samples in the `samples/` folder should be used in order to determine if the library is functional. + +The unit tests in the `test/` folder are built by the normal CMake build. The executable can be found in the `test/` folder. All build configurations and unit tests are built and run by Travis CI whenever something is pushed into the repository. + + +Alternatives +------------ + +- [OIS](https://github.com/wgois/Object-oriented-Input-System--OIS-) +- [SDL](http://www.libsdl.org/) + diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..1550fdd8 --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "com.example.gainput.gainput" + minSdkVersion 21 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "" + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + sourceSets { + main { + manifest.srcFile 'extern/android/AndroidManifest.xml' + res.srcDirs = ['extern/android/res/'] + java.srcDirs = ['lib/java/'] + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:25.3.1' + testCompile 'junit:junit:4.12' +} diff --git a/extern/android/AndroidManifest.xml b/extern/android/AndroidManifest.xml new file mode 100644 index 00000000..e477f7e6 --- /dev/null +++ b/extern/android/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/extern/android/res/mipmap-hdpi/ic_launcher.png b/extern/android/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cde69bcccec65160d92116f20ffce4fce0b5245c GIT binary patch literal 3418 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 O6N9I#pUXO@geCxi=Rj%z literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-hdpi/ic_launcher_round.png b/extern/android/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..9a078e3e1a42d474c78470a73c7987cf7ac5d9a0 GIT binary patch literal 4208 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 ADgXcg literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-mdpi/ic_launcher.png b/extern/android/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..c133a0cbd379f5af6dbf1a899a0293ca5eccfad0 GIT binary patch literal 2206 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F!lvNA9* zC?tCX`7$t6sWC7#v@kIIVqjosc)`F>YQVtoDuIE)Y6b&?c)^@qfi?^b3~Wi>?k)^q z@Y8vBJp%&+XMsm#F#`j)FbFd;%$g&?z`(x1)5S3)o?RaSfnpWT~3HxIK{-!Xu$=cg)AFmYAb%8b=pyq1VH zd0f)UT|7l?>LiX|39gOHT(_8-@TnDk*i|Z?v%bEp?A^=5XWpE9|4xVT3Oip^S=qat z-|O!G-1+=o_Jg)N6J9GldbpSWIOB!V8Qo>??#yCPP-5&}^tsvIZp-4``OhwGpMUF8 zNw4Px=H1t8mgRPzc~}4TU%u->@y-Lk->0y7ELisU+$?d171t*;>{?X*PEoUWR{8xM z1_DPT0y$0`X1`bQesZz!@kgb59YA0xj|zP095(#@}TYF|IjT(kSb_s;P8>JNJ_ zGwcxBvErdXd`)!m^Tqc2Ut4GUWvfujRzoy65!WS*G?hOz2n)oA}0!hdWr9N=}`7 zc4a!(^qnRyN{4bdEZSZEGWIrqi99n0&r6puQ^w!*o4(Zjx^Z&f=J$MGnP+ry zd97?a@ad`V#d0)4_1%;gS)$X&e_Ge`@^#+`8!&Ee!>c6ZXl*XWV3C$T*U;B>D4)Ul0DRTC>0GA)krL zEd~bPld30PCQHn-c>DS2BkRfgnKno{*k#PwARu(#a>vea?*j1|wVk)7G$!=S2jOxkB{t}FiX_UAvenDca6iE^B#EzzSfnzil30< z!jNzG+Nl4(PyLHayJ!4PR}f%Z{L`#dol3x7$+9!#z4!XQmsjRnl#)iv6qm(S z@tO&9r-N-L=Io5h zS7Fi)E4h+YxGJ?E<$&LV#0Si$<7Qs?7*@U1ZOL@KT+a|ChqZIRuI%z~KF}Vy^VS8< z5cYuWix!H_dwS*hqzbRzr<+<6ohJ#T?3kje{V+1}PiBkuhRQmHq|YU64ox@iPp^&j zOuV_EsJFaIo-s}B@PcbIJwLB-eAaWclaa0EXBQ)fQp1iEF>mn{dm#^j13XHMN)ESX z%>65&uwq_;mMwSdfsHfH*V!?3Dec(Sw;;ozZkBIDfmNTwA;!KNyNWlQ;Ns?)najh* z8GYTXsZ>b0zOa;i-cwbx=fbUf^qKxduI11aQJAQ-VwOW)SL}2pmt{}q>_2(?-39G! zi@%&XBj%`Iaawua+;v&Dnq4b=76e?Fx#mir>KVzeH!t0s&Y|GAfw_mJ`G%7lU(>c$ zZ{M((jbHEi9$@j;GoMluP_oZoOD-d+&|&q)Vuo*=GDohJyy1{%(e3ohHoEaxGhV(g z%-qQ~L4A4fmK~Y8wbOF{gcTKkbUT=>sPs~*d1m*#V&~N*9$NeFxb0$OW7yiZ;Hax% ziNjJR_lCop&TU=Kaxb;$_0Oz1ADsA16i*onhbo?5s(NATkt(;1ldlS%O_|uqw(pag z{wDT0DqCh$u4OgazjMZxZ8uW|_gsFlZMwt7gW*Sn-!y!hD|*%?<%OC?b!JKBq6j;g zhlhpUc>K_K(46OBHq48pA`R-(z%P{IyF{x`iZT% z^6d$u=yc!P+GTbujQY&?lvcmqJe51rp;X`B$uPTV%F+vp)}AXxeyc3qyl86gcZCl* z{O<(gIbJgU?)6d%$=;lrYj|eq)-6pp9=r^0WdCw#&6l%pYd>zc=Z&3pweB-Oas||nTsUAC??kRQ4UaV}{{xw;L_Sfqw zr{vyv)7pMA=KD=StH8DIuAK6HHF^F2oCnhKd+J~6Fx<6W9xuZ1bynMKalP!kAd#nC z;aZ0^Vm`92Oq9r}@1OHG-|hNG!3G_MaEaEXYZwBWvwy2!U2(|Z|D7q{oAlikvVMP6 zDRlm)|Fm9NK7q+%$EIEe1_sp<*NBpo#FA92PsvQH#I51%te>D32!p4qpUXO@geCyMl>}-4 literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-mdpi/ic_launcher_round.png b/extern/android/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..efc028a636dd690a51db5a525cf781a5a7daba68 GIT binary patch literal 2555 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 literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-xhdpi/ic_launcher.png b/extern/android/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa42f0e7b91d006d22352c9ff2f134e504e3c1d GIT binary patch literal 4842 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 literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-xxhdpi/ic_launcher.png b/extern/android/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..324e72cdd7480cb983fa1bcc7ce686e51ef87fe7 GIT binary patch literal 7718 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 mm|GbbNNw4A0#j?OkpvfU);r4(f?E+p# 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 z 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 literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-xxxhdpi/ic_launcher.png b/extern/android/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..aee44e138434630332d88b1680f33c4b24c70ab3 GIT binary patch literal 10486 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 literal 0 HcmV?d00001 diff --git a/extern/android/res/mipmap-xxxhdpi/ic_launcher_round.png b/extern/android/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..34947cd6bbf9c729be83edc96ad08a1d42b82bc9 GIT binary patch literal 14696 zcmeAS@N?(olHy`uVBq!ia0y~yU^oE69Bd2>3_*8t*clis^*vo2Ln>~)t!1wW3H`Ni zOG8MelCMBOL9WK{%c-TYclPh{@?;DQ4~%rZP|CeqE6G`*#OeKl5{{N>6C6A|-p?(W zG{H$ju~9`pB!FYi|H|`ojCUIEG@f~Ki}k~M&nxFkIvEwew|xJ5?)Ts4c0RvX++Ve# z9V=30S6m|LTHGZjqkFdHhjopc@mANX&M#HiGov{#-FmS5$oDOiI9x>At1NUDnVUb% zcer*=q*jA1(8lqj&e3iW{lGn^J_`N6x%p2y+sFL}?{715mRY57<*-`d_N5c=XSy3U zJWB22*GLQa<5Jk7)TcE4k<8K5E@=^S&Ai|{ilxV8|81WAr(Eyz{*zDV7q-+t!`H7W zj#gb2>$T$2D#2e1=EumdZ12_K@3qOlTt9tfnY-b(6+E?zqLgk&&J+7ntKL<*>Tci~ z-6;KMd`qTUe%;XYbZ6;nF|mTw6@l>|7d!s?`uh6+6;VpsQF6CVOn$k)UhIf_Ww7cz zqv_!vwyN6y|Mhx(aENz`;?JL(+ElZyl<&L9@VDgiv$IR>?Cf+y1WlU$n3|f#W+?x9 z6b@QzjstP&2-hRK%+qVASpM+2r zHJ`kTiNzAN@oRqlT^6YNsMaGRKznDw!$X^2Etxsd==s{{?f>L!>nBeBzbvbAmQP&4 zY|HP;M@r`D=!uQw7)gUbBNk-Xr_d1>UZx5uMR!*I_ zdx4XSdz17PzP+9M_Gh~@z5D6-&rs{%HFmso&@{dc4VnoyK?2q6KBurY6^ljreSCw{JS3JMGLxgVb@o3;8}4%ZX++Y5Rx! z`@yB9-kE`cfp=#=VX`_|CUy1&tNezg&)t(33G2u2by4!qRloQ0^2vE$@;yo(<`q57 z+8V~9^|Dm4>DbfNp(=Y4tZ&Zz#(Hz+FMFY|4Xq*9V^ZFxtv+e7DD@z29Y{OP_pwecdxHlOCGgpIx?Wvs=CI zlm9bS#C}c|HO!xodhuOK<`(HlIpe;Rd7SCFS%(iCoY(L0*(oS0Y8O+?#m9b=HOl|R z?s~sW*IG!0|NPf$lj0M?Urk@K#b*A3g|2=JCdEJgc~MPO+;7s_&i`po#2Yvc6_wvq zE=aI6os?Ac>dMM`lg=d(K_7qG|N7SzaVPDyN1L0^>3*fvi|=0-JTPkqr;0|p?9PzT zbzirx`g$>9es`$2hr>0lO*U0?`WU9U_NcG?IdP3|u5Hnyxa69;)YMeps;Xbye!5H( zPXw5jFA<-X)gNUq)&KLx>FJ)14?Q;-pPIa;>!K{H*)e~mmq!JkJfB~`@AcXV*M42t z=Ap@_xH?(=#e{m#CDu3P3@2|@y(N;H{ra0zrO5M=cXxL3$ji^yOWJlra=N9-<7?@2 z_p0q`VOSTl)5*)r%Om2C;3maS!IIi0bEm}X>|VWg!j)e!J4*VZ=C9l}aeh+ijrJ|P z{hPMA%)X_a;J_k~nwa=dOs^r$Zz}(Xc72n@yx*3st=%8vvb*Z5mJ-Lw6DJhx7QTMf z%aol_BvV(UzH(no>GdeZ#M)J>v^_oErA>SD%B|k~k?^KzDQRh7Qwxuz{AAeb^y6P= z49nU%Cx4l&UbX6#v;L!=j(^|YM7V727T2Hj=TD80w7l;%+5hEx0~4Y@)qC8#DKhQO z%_5NrvqMeUxApwn@$HUrXG-uPk-bN*&t1dwYswMTBNLjMn66FB2(D)1>S3}tzB&E; zJbq5jgU&XyTHD*VhwS=z9WWo3I5x9r$4Lq$z>g6u->j7i`7 z`WOuSCxl3xX#0LlrQ(;%biMrto->-~-AQ=f#L?Bky&(9h!aw=_-TM#T?>axR%PC%> z_vGY)vmaUOQe59i?_%nZ@!rOEJ~6j#o3r$-3cdvkpNl6U%G3!>U zi0e5J$bZPMJZpL^W0sBz7&1cy2#Hb7xMVC-Ct9wqEO9gIc;-a z%k%dtCso}&&)tj&ikWxV?(w6ipL5#8=IgdB-0ye%%4e}_&#&zC*<||jzSJjfg`~D0+>y(v3WrYHsa;wCbE?uglXeY95 z>YAP^|NOiMu|hMpPK#Nim43uX^#O$mE4@yupHm31E&ET`?B7CNiW zYmSeZTAjx=)jj^g`8{^|lS4P=Pnx>%v7|ul(gWwij_y5LCvJRQT1b78Z|CF}>AuR1 zmf0RT59`ddqoWEZ)vE7{?h#tn!T7)bzWdq#1;>nL{n)oqxnXHy{)Fa*(Gk%L&t53= z(hy3hV|ZTl>B-5j4`$rRaxJ{p?za57%68LxVNOe#RTeH5@_v>iyy(QV4evEMOWhlN zJLM<8x%~0#qPbVnJd|ziKEJvuRkX{d|A^Rob@!ralkR55PweHC&zc^;rmMDF=$9|& zvFpwI7C;?e8p9&k0fp0>7&9&F9tKVj8r(Ip=aaSAyrZ!Euc zU1wMJiGRz)w0ReOWD;;#8N^lduF7iH8?V1jN9KH5_Kt5OCRfE$9rt#?jyA%P%I)Q5L@6CiLjl zt1PpHlc!B{`_{H@ojKQ<#nY6H?$18U%Mq=()k57*Uft1h^~q^V&aYUy(5%a?Li3}n zB15C7$cFIQlkHR+cdmZ1vn@a|LuZnS%pOMf+nOv1_;NY?QD%v!45>yy~0V zLV|mIPyY4KYJDWZxb8}fV(N6i$x9wN6e%Q&ZS|aGq_u{lndhghYQs~*Am8diM%^eJeW@9b-h z{Y-t)W@l?7ilubzLXy5*-|pnGl(`n|+hxVMD^?|>XrQv#QX0eB3rkJOWjnlVf zwa0aiX{oGl`oSgMAhJX5{gw4Mw?3JFPUYUo6+ip03NmE5zdCeM~Yi50`e$L(~ zYrWb}RYm1byZRA5zTSTiA3l8hvUBOps-??K-<3Iiaq!)_Vnv6F%CU)S6^l>2no+L8 zR54qv{rx1{mG=Yc{|iPc_x|wHXh?liDzmlR#NXp**S_mp?M`QWxi4hWt)g~xv)ROJ zf2QuT;}TW;qAa_1)ARH5@6WWJAja4I?@Nk^?BB0z1%xzzcwE`3oUD}L^k?O?KSc#) zj91*}GK9(e?{GQl^6JW;xe0{|(ySNS4Gj&04?6A+Z*loD-?BJO`c}k8uP^_Oy?Wr$ zA^0&UIJndQMAasvXZg=2+MoNxa^oS>f{XR}C!(&g&G_g#Tf^X^=?)jB#SVUcezR7u z&Su};&s3wf&azkH@f^ipul1Q5&gqVj==O}4+X zCry#_-TPnin%cLB_xWeL1d7B?xvmk*&D(x6(ev(p)m|~ib3%WYb|27-U#E7GRWYUf z)AcLQ_a{$%vEGDH$M`7I#W@M?k4$cf9gqBZeO5QqTSKmdbxO+0%2SFi9?kpAbm(_@ zNQjNToX^@X$0u`rO77TSv$RV^jBSGM$^TdM{5QCLiBDDgIaPrnjCIlcjhYXWh3Zeu z-^zRIgr`e^^z(?Ipdw=hg$L`)R&x}tzuLq%ef89spA%#MMVHQNnRmy>Os(e1jw9iP z7w;D_ay+Pe`CyLkn%+67Dh%IOt;%rl_PN96cgu!f;GVs<_G*3G#vr-B*>fBJOuZ_8 za^st=Ig?IE{9G&jcdiRVji!kj8}sJy4cm&?1RJVPM1T79>0JJu9Ulb?iVwH(uFcVH z5HtBXS7+z>kA0I)_GcZu+2tfOVe6bVy|XV=e7eud#1Q7P-;pm}PwBZ<*BnQdH7Z?O zy#)mY)kmSqLrAmSITl(@0@#=VaXNUlnFe^ ztgkzT)rIC&zuVdOKLfMk|H9hxQ$qeu%OFty;-+*L3LZU;;B1# z?rd7>J>4$RR9fW?ziiyS33;DZZkc^AxV!155PP6cCF9(z9_SQ zyYDOp<4}ehzDi49><}uyp7y&l$yz)<{=VlPsV7M)_{%g- zt42@ypDOw(-(!YH-_vz!tf`tgttxqoS-SL6CS2I#`{ei%O+K~#iHr&S3n%R9{Ssrm z^?7gh34L!R_u^?Ae+qan+HWS`G2f}{bdkvR-DW#Ft^cjaj$}XAv|DVgfGGEUjawHK z^<6xl`oG-F(3iU2`2O?LF10x+Qv=?dE(;N0nZ%TG%0k#}o7(@&%bw)>e7Qg84}V!+ z#4FwMccIhuC+=AEH^uqUeO)nzy5||svUb}V9|*BKz?PV@sM0~^X-;*)boPSoKlR?f ztUNsb#aC4)KYJ-5^;0eS&Bu+pUPYbz{jN+u{8amDPlo-a_ciPgVxx7bt0KmE}UGw=O5 zSk@KsM@{JdrtYOr5`Ni8{nho)+xXSf`N{M?mV@fowbXvG&yPtr_;g*KQ|shC;rnJ* zzr5WWYt_>xZt`C0wQH)-(||k51=Z*0+2%LzE@8Iw38a^}a9lOu~gN;dB5IcpK!{ik>Ck0#!oA+-m6eos5~O?YbvgZH9X$#2X4 z9{A^)s?@AW$_5gZGAD=UB2%kKR+_1v2GKWjGG{d1n&nX8qM?)1rQ<820Mue#(6 z7S~5P)t_c>dy{DT^>fE7Ue&`pwk?>P{raZ7_oDq}A{~{}zxFOpZ?9B+!KdX|S63Gy zd23l9(~s->|6P?@{Yo!>-<~5EU;kg~U*~g%|B73Ec5>|@mm-^d=C4xOUo{&l=%%WHh~ zVvF7^p1Ok}OYz8w6DJr7W)$BnVeFB&mwR3R!ToJ!TK%1ioEsv(O^Bc6Sm*Vp{i5Hli=Ar)}P!e@9Yp37Z>-o z?1@&1F7%rn%2X<8kl>(hHAmIZ^Xj#GWlR1hUhUq)ye2r;vnpCAcf+*z=I#vB^s4iE z7&a^q_~I#lEu61z%d6ZeDUTmN-dy$NMWOwUoG{HP+zY<)+yA-Htz~(AB9p+@|DU>3 zkJ#7l3{-)+!XYKl5H85GC#$RV)ZRx>_R~OutXbcJt=FWT~&!RV5xgl*&-mR*? zzrONc=GQ9S!tg+|`~1_1b2~4uam%O{z8~~Sf4kAoT-AnL&iR}76@5?+yXpH$Qd08y za=*D&A~9y_f?`Y=j~_q&%<^{umpiA!t}lwKHTu@{)*jdF%$NE(_xdjnRtD)QkutAe zcp4tNdgsoaQ}6fxud~?E)4p?tPQz5~@J&K57CN`LUF6^O#)GkA`QGDC*(_vV%6iqY zyVN)Ckg0zg{7hDnp+I!=L3RImYd(JYBEzU>?#1Bf=y?7j|1VbgXBvzL{wH(i{NG~g z=U$UNAzt&|eHq3dOV=dTw(Tok!obDFwN5{7&yUp2tOx3M>WVU4W4v21h2h9#*^O`S zZu_A>r|nC*@z2Jy8-pX~FMKaEk-cGcf$nm@xxY#;E^=Mpc>4d}_w~N>c=L`e@4MaB z(( zA4rwDa(k9m!!sv_<9)Kt40Xjz7z`GN2h;>vue@y1k>B}zyZ+C!z5iypRM@=T9CNLl zPwaqiR^t*TgO~dnp50;E`uY3!`o>BBcP(a7P&;7sN%-We8|Cl0K1GZC6czZ}_%two zgPpK&5}(L{-pbFstW8CM#b1YZm4l0zT5jKdw7L1@svY}HD^D*r z`DCv4i~01T;}b1zrN6Lt4{34{nlpcX{oTu#KZh1netL2;_pDgM_U+%_zhn=y4q}M7 zDn4!F^tBARrvmSQZOc3A1NcKi&O)-|z05OfxJBl{P!P zzP`S`tL_G?`x)jAp%+KH#XTQ%_U1%37Yp6zdlKI9b)|5FHK)(r6Ti+mE)6(#_05|% z-v57|ua9Gx{xOGpGpj>;|G_JVr&>5MByqY_R$6}ak51iwY3Zkq_cqs#cPD!;(f;Tg zckb6YM{lJYK^?;Cesg|5Xy$L5soQWpzFt;1@$J3J=X2{yI8GNXVc78e_ref;uYDi= zTxu?#*nj8diTp(jdltotel2rZ5;xY>K}9P3??o_u*p`k1h;znZXvvw&3T!jb8=RxNLNeDY2FRij(V>xbvd= zaD>RSTN*k%(#rh-30ri;3UqiDuNS%3SsZcSGRcVN^g_iwD^{%VIrZ?~gHOr*w$GB* zF-$NiS-oY0DK8WwJpyIK0@Cw22rNBt{GRpaxnB}g3)kG}I{ zW2oF^)@_XIp1gi5<#YW|@59%!GS;!G1oX)-dNbsuD1x4Hj0@ax!Vr=a=oCyuRGi;pCUzmW2Bk>rWqw zmb?G{;e#hhA0{5^O+04T_jg^?pAGkW{}deP-Ft4y|9L0tmK{99ylY13gne=)s}C!N z$Nso<|6{50=06%Ys(I6X_gy@2e(J%`2DM9PlveN@esg%S=CO>2FZp}SIL^M8KCBUW z#>ys0?jO@)i8OghR~u&ciE|B&Bko&zG2Gbs>%>Ime-8|v7kN*f?$jas@I`ibm0(=o z4$U~N23yX}EB}5_;O?soI8=Gz#0$U0N)Dbri&d+i{-|5OTd&6ATc$zt-d&l7$@a$? zdFvjm_`Tiw@R??rb`8f=gW1g!XC!T#FUQBd!?@zt?w3jn?4B9yDB3Y`r#r*1e;KmR zez=9!FaF3Vybrj+kCQSCdAyG^bnbd>RDwk z+Q`SUlK&KQigOOE4OLfr+4Z<~KUx_>ZPsK3;tC&xDI9 z;d`7}3!a~uX`FI&|K9NBZ4HQ{>i#uAnZCbM;V zWxS`k1hn+4c4rv9dij1`wkDsF(C*iVw%et7KX`e(a0g%UAJY#{W;}j>(7>Fb;CWy~ z)8cnLpDmuTT?&^xW3Tq$;0A>gvE4r%Hb_Sb`24Sp5$EpTohjfn`JTY68*Nz|CaCS? zzF8g4wkN*h`i5f(Jial#iCb(`PQI`46zVzu*>A={P}&ucP8JmtyBE2$=&Aj|#PIOz zd}-Vdu7=0EioV!)cDDKdnX6VEyI0vThhdsq-({w$vn;1f*{pi;=H>Iw;_a4yUK_ac zOC<%%Tx8gJEJpGS_m>M3e$HTva=I+Y8F3)~IeYd72Y&8;?w-^Lkw*s^l8jrjHh4rc zUUJy~>#?X`d?Ux%)!`QFf3;*k5KWX5Gv0AZSw=#++m$I%D5JhlYrVUG zzFrN_d+?z{Q2Ewu?S^NE+xd?laa_)C|EJ)?`u+c+a^8DQ*vBx@1p(EPs7~P_VjOhma~2}=jOdd5^Imjv2!tGPvDca+9H4FsZ3dB4@1<&h0OIe z^c!^2rFQy;HLJTb9;AJ0!-E`=Mh?SHye%-sDLf~u9>>tY#q zoaSP9!tPLan=7GbGZzErhq;%R`wP$7t;bMq*&aPpo8e`Q!**tNz8ziGhu=))n61sA z$P^Ocqqd)u>4IXtfqGrR-xv8l4BaaK?ge~_*ZKJU7|RBgOJ|c9Z_N7BCv+h9--ZWo z-n^N;IqmE(hC8+Ic0RY7Wz4=nnPHx7`Ohzx{pT;~ESP93{O}lugLacbzng1)GgHF) zW2_49b-tm;?^b-d&UN4{!%tN)jZ16yF}!i9TgvTlh9OxnCMM?2P4jydkNY32+c`PG zYUu^L+FuKVO60{vLc_!5H>WGziD10adBEh;X}9{^wQ)?wEDCGinP@QN{=9T|1;d0d zQBr^3y4JCCEtsa?ctC1F-GjXKaeHsg3TC>h*_z~S`ES9-d919g+;3*GM<_8kD^1vH zCu7xgLA=54Coe}rcq>eLWeUUv`b-r*FqUg?)ve zPIDeOtHM|j>B#V{=MUc*#v^$JTZ^Bc`^z&&)~=>v`mvCP5-mrY&KGutkB%(7zCM0? zv|K_LgU>;sFj*r#hRuuim$_|UCv$59!xyz}OC~7p7oYziX+r?Rw(dX2SQFk?GHg_> zDk=GL^Ll*!-^zk*8$}i7x=6^aa&G5~effJ|YHBL`+pQ{xqL?mpDctz`xeh4&lon`Md}6;v)|6OQ1)+qT1rJ`omS+0Tf3E2N{Y{47+-Gwnr^l{Z zlJ{k+_}O*)j~FEPw-#O#VQ5$??8I{HgueLSujzH-GerNszA%Ad%YQS|U&V|Ov;Rzs zkay>B*u>DcCU&>j*Z*<;_J2+CS~4%sV2Q97ER(b<(a>__PgovnbtYy@JMX4#3`aZ| zekYyz^+LbeV8@q*j*M4y^Czh8pT}^ddNWIc!Y}JtfA|C#qW@2xGbiTn$K&$5n^ybV zd~`|g{GUICVN0dM_jh-1e~v!=D_B}2r@v^0E{lR2Q%DZW$JFb8SR~cz4`1B>@0VQi z#d!H&Pt3y^wyFM?n0zANpRwbU`fbNM_wD$~fBj_kgzA@$&1{YgwaH0I z`yR$;T`tO~Wvqzc^C^0K0RL< zy!^y0)9gJ50?%Eo*8TYZS7pNt{uidTzrF}wUl+UiXK-?kCW9i=gzEx7eK*_XeLbPC z&fsiqI>U%*hSXnK&pLMQhO_^U9X}qv_y51&iw|B@bZ#qo`t`rqG%g2g`3p5@V|5fm>*~rJi@k4wPU-UGWw~7pALM~bFPEAtX zAI6Y(D_WTK=Tnx16ThWz)i3|}M4Tx=?dMwyrVS_8&zU2$sp8|K_XmAng~&aK{jJ%z0y=p;-ieBe zPFx$k{T+k4rMY#)<$qT;J2JELxyW6Zu6B6+)hkzGHcsW8B-+r+D41}(_1J{@Z%-_^ zJNMuY2A)axJ-%GuS$lB?gN)2iNhSANF$_sBi=CVfUG=Vd48s{E>HlO((zBg(aY=9+x-21zsWyH+F$p#s%iGM8Go`0 zg4QG-@AGK;9bgj_5Fl{Sz46vEC5E#e3qF2P`1j}Wx^3$F=P@KyI{Z7j%%#5dhFmp= z!|cGFI4xN z2{!l{`G~fQE~;JVV8wOh<8sDpx11QNC#M@Z-J0}T)0X`ZJ!4+J)Fh_#vC`z7IBbMsqsjkZ(slj&~t>I(aL-5ab09HXLc&5higR?GU1 zSHgf{c6{lk)KA$Ol6l9cwhLF7XlrZplt0OkJ0Eg;35$Ygqv;8KmIL2zUQym3`2M}h zlkKexE?&Blj!yxv#-{@#c9W-;BUFLtuC>Na1KQe4XE zkkziU*=`bVy0TBtKbdn*b(kxSwrmXlvOZj> zmFf1D%**mS9h{sF)%o~#FaKg{3aawf241Y6_%H7?uWS9AqnS!S_lmgub2Srk@bu&? ztFd5P*H~=x%iDW?nQ((g+tS4c4rm51+f(-I%S$eetCvijyhIxW8a4SBP8Nzku;hjQ z-JWYKj1kR0uBq%dWnJ+7%=%N??^R{%>{urLH~QA(KfhE1zv*mbm#-;csFjrw5ZJ)u z&*ARYvo~tZ#}1XHoD2L|+*kzSbl4PRNN|WcZo7ra#TT?()fe-$%Eb{v3Gca++0Pn#~8#^>h5@T0LBNoq2c3%S-Ly zb#LDNed@;e(dXg1B#{N2ACDNPpS$zK_{er?Z3de}f!-bVg^JOO7l#NwSgG+Sa+lg< z`$c*6bo4eoTXSs417sgtZzw4=N zY|otZ$)~ES%50jaNm?F5Z`<~$GK0j zJ?q!;v!GSDev+DDppCr2X)eXiw$`TUXU?2baaXE2@##Q+M9Ws`g=dxO=jJB*2j?{W z`E||xx3RlRE%!-#`y7?XsyFYSc>Iu?GzyuadT$j1VzA z#nN*{;f{gWsRTyVzfBhm-)PRs?yEWZi>_-@(nnVe$Wz_WB-{a*Ot= z0>7hvvFoTGcVdp1 zzaamD`wQ(alIQ>WeEU1YxTB|Mk74bvFXs0-e^1dB}6%`iSjs+yTFF1Sg+Y9C| ztiM=lo9&$97oPX&{MYnngO{hLXUfS*s@exd&&{+umr&aisTd}D=#Z+oxOmJ3;dXxc zd!bQLUB?V7^8*6~n_3SEbne&J&^U0Jfpcq^&L;+my3iYP8Lx%a{WffKmtUZNLH&jF zmx-2BQOKX7P<)X$}&^Y`qrS@Wr9 z;T;JrIz#1)*9fTTZV0blpLciH#+rl+3moeg98TBLVtuA|R6e`e)R<(IC9_LRTBw}z3KZO;Mkd56B2Uz`1*NQ?2rT8Y%348A!Dt#Z{f zIDk z+P1k<`8Pys$X7gIymRi`j}}hhJHkRj=MGt4I=1`j4Ax*Rnf_yWS+1WCZrYf9JSlwp zaZ7c%Co+Xz_w&8%6t+dYzrH>H{_%+)eCF9y*2KibL?*i~IJA@DQ*vs<{{8iqybbT0 zm}`H1seB}Nwt4OQ=Q5d(c~t$2*G`xr|Kf~E%fR8bd|lAs_Hja&pM&#+jUe$Qt!t?Iom1&L7!PBFLiAmi(YK}buy~j zrZ=wj?twf3m#ZhvpI`6eQg`n=f1zH-$#r4Ay?>5rIY&JBBGAv|zwhU>+3y}ce8`%i zF)QY7nCpW(6KuTSnaL$qJvh*)WNvO=@lQQ-zWvFQ_cv>X)aYpS2k|`M{q%%ef6s@a zgoFgGQ_1`Pe!DGOZ8Igh^{rfdi$dG%sIvF><{H-g_;CHg@x9gGk11_uta|DZ(uPAF?dCJ|X^{n$${;EtZa}!`9BbxwH8ByEA8e%=WJ9I)%{(6#MXg=}&42}rD3p`h70*OW)tC)re=k;~(@&hsrSzN?v(F|_u6eM z=HC-t>X+?1t@P1rrw!~+6kaLL7f|}~WU{{<4+qDIV^Jk*+>U(NWhHgP_SOAES^;Yw zfB*RLZJV zdA3>ZEjRb(*Abx|p`CZySGjDJ{TM0z*il2GapH@0aeJ%8-*($C@V{XGLOJf)o3p>y zHQ&1>H}y|i#{$!MF>Nguk0o+~m5fT~(&yKjojiYDpLZgMdD7?Ky`hRX9xS|~VE>NM zA-DegJlmI`CaC%g=7-+4>JAP$Ue_?#jomgq*^=Dty%Z-k47v^1`B>zCP%~4NJum7Ur zk4!r$8{^bdQ~F;B$j+NEp+K1X^$vk6f(J_;U2ppF;pdH+mAAL$PMoafJF91#tMuZv z7oXX@vOn>Ak@0)!tM6|g6zU9GSz=PLr1<$c|83dV^HvBP5|6JjEWN+4HiVsXdx5a| zHBryjgx>a_rlyY;&uCcWR8#Y3ZS3wcRlm7bU+bHGWJGT<{Qm3Giu<0M7Wi(pDbt$j z<<2}&nrF$xmw`Wm^jLbh4lA(a=fBs;>di~I92RG_S9z;@`DBSNxqQ9_?-wlTR6Tw1 z;zjeaH#Y>&cG~;gklmtrODtFCor`i62$`dwby(tj$_&yUZt?oY+lWxlij*e_%6|FLKn z^X|{qdF8)8n!P)9JEr#+pGaff&XACF%Z>NQbYvzq76kB=)V z`jo43rY2-U)Dr97oiC#wE=m9D<5K;^?aQrfep#z2`*Ut?@?F2@Q&+p($M&N6{|)j_ z-`kVx+UmZ4&&o;n4=!%pr@JeDlDvUpg2eKQD2~QoH=pnS^=fry^!7Yn9zH(3!{6#Y z?BCO~YHh7`x5AS}HtQJ0oBptGpPjUi<+_*qA%`s9{Z7&xj~3_dxGH!=-0pv!QnpNyyD7&$6O;av;eeJE}JRM`h9tp#wEz6fbmw8q1+$u4_BAHp^hKh_* z*DLNrf{QNverJ8D`0&n0&m$)Hd+~Yr>-ydN_V%`O`q^2g?~jVdmrQl}y)c7&%Sqc= zbAP)Wd9SQk$9`{_URLYz%AAS&1deoE=>L85ph;kzkX-mAgJzNQd%j%qUT6GrW98>( z%I*B}c2g%zDB#`xHSwZ&*rG))8p<4ncQ%&r%PQBe|EpwqDEY~uylRgxlP67D_w)Df z`G-u;%rHzg+pA_i?~2i*$FqKJwLh8t^U&k(W{p3ze$I{HxLc{z{>${*8k;7Sexb&j z%C&}H-ruihS@vh|o;_igFDkMu>FVp-*Ohv6#jFQzvQPMT{O!N3wv0E=c8i!-($XHe zU;Fj#+UwP9Y zGn+%VA2{AqyBqq*yjF3`^U~MrYM9(_D4S_DNGNdRG^P~!awJ8}x2tvA^Zj16uwdx* zwb9#W%S{Xs`6(zUIJL90^SYzk9g!xL;2bIKZ|9F3+qfaPXmR6>Pl8TO@$I#Ze-BMA zHTdL^BheZZ6tvFW%`Gfw)APr>3;8Uja|)~dk$v8lsu)z=oPA(x!KMFxLT_8oRyJ&R zRljGZ^>OwN0hdiWrJi_Zmrn4w=$&vOYPT1 zdo|L#o`k9Xi&bb5V{>cOyYv6slpY@D$|Q>xw+qJ|wX&3FOl3HEYg_K`Ej=16ZH1HU zZtpC9ZqF-e^kjYgkHhlE0+{-%pH2-w)-PxKOPAsON8>;8b?p03i_iWW_vrTgi|W?< zq@N#HZ9ZdVtxf69KHq;P9{Kxh5;(B37Bow6kjOP-4@H2;1w$Z>-p z(=^WSdyDFAr?@@4|NZUl@81>Dmix_}cTu<_^1%H^#vdYo_|%xGTkXC+?RSOX!=)|t z6Xt(r{v-9p^L~{6t<|Bjr6CvkxPlC~JQjOs_il2lLRF#m#s2H3*o(S9a;QHR@bkHI zY9rfo-+BM_!T;N=|F2p1XY>2V_pdhAmq&ZHOjq9HRrPg4m&6xVMfEPTIX&OK z+ + + #3F51B5 + #303F9F + #FF4081 + diff --git a/extern/android/res/values/strings.xml b/extern/android/res/values/strings.xml new file mode 100644 index 00000000..ca9f0a8c --- /dev/null +++ b/extern/android/res/values/strings.xml @@ -0,0 +1,3 @@ + + Gainput + diff --git a/extern/android/res/values/styles.xml b/extern/android/res/values/styles.xml new file mode 100644 index 00000000..5885930d --- /dev/null +++ b/extern/android/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/extern/catch/catch.hpp b/extern/catch/catch.hpp new file mode 100644 index 00000000..29647904 --- /dev/null +++ b/extern/catch/catch.hpp @@ -0,0 +1,9427 @@ +/* + * CATCH v1.1 build 14 (develop branch) + * Generated: 2015-03-04 18:32:24.627737 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +// #included from: internal/catch_suppress_warnings.h + +#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Much of the following code is based on Boost (1.53) + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#if (__BORLANDC__ > 0x582 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#if (__EDG_VERSION__ > 238 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#if (__DMC__ > 0x840 ) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ < 3 + +#if (__GNUC_MINOR__ >= 96 ) +//#define CATCH_CONFIG_SFINAE +#endif + +#elif __GNUC__ >= 3 + +// #define CATCH_CONFIG_SFINAE // Taking this out completely for now + +#endif // __GNUC__ < 3 + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) + +#define CATCH_CONFIG_CPP11_NULLPTR +#endif + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +#define CATCH_CONFIG_CPP11_NULLPTR +#endif + +#if (_MSC_VER >= 1310 ) // (VC++ 7.0+) +//#define CATCH_CONFIG_SFINAE // Not confirmed +#endif + +#endif // _MSC_VER + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#ifndef CATCH_CONFIG_NO_VARIADIC_MACROS +#define CATCH_CONFIG_VARIADIC_MACROS +#endif + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// detect language version: +#if (__cplusplus == 201103L) +# define CATCH_CPP11 +# define CATCH_CPP11_OR_GREATER +#elif (__cplusplus >= 201103L) +# define CATCH_CPP11_OR_GREATER +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { +#ifdef CATCH_CPP11_OR_GREATER + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CPP11_OR_GREATER + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() { return m_p; } + const T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( ... ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x00, + + ContinueOnFailure = 0x01, // Failures fail test, but execution continues + FalseTest = 0x02, // Prefix expression with ! + SuppressFail = 0x04 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CPP11_OR_GREATER + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator->* ( T const& operand ); + ExpressionLhs operator->* ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( NULL, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +// #included from: catch_sfinae.hpp +#define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED + +// Try to detect if the current compiler supports SFINAE + +namespace Catch { + + struct TrueType { + static const bool value = true; + typedef void Enable; + char sizer[1]; + }; + struct FalseType { + static const bool value = false; + typedef void Disable; + char sizer[2]; + }; + +#ifdef CATCH_CONFIG_SFINAE + + template struct NotABooleanExpression; + + template struct If : NotABooleanExpression {}; + template<> struct If : TrueType {}; + template<> struct If : FalseType {}; + + template struct SizedIf; + template<> struct SizedIf : TrueType {}; + template<> struct SizedIf : FalseType {}; + +#endif // CATCH_CONFIG_SFINAE + +} // end namespace Catch + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CPP11_OR_GREATER +#include +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern std::string unprintableString; + +// SFINAE is currently disabled by default for all compilers. +// If the non SFINAE version of IsStreamInsertable is ambiguous for you +// and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE +#ifdef CATCH_CONFIG_SFINAE + + template + class IsStreamInsertableHelper { + template struct TrueIfSizeable : TrueType {}; + + template + static TrueIfSizeable dummy(T2*); + static FalseType dummy(...); + + public: + typedef SizedIf type; + }; + + template + struct IsStreamInsertable : IsStreamInsertableHelper::type {}; + +#else + + struct BorgType { + template BorgType( T const& ); + }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#endif + +#if defined(CATCH_CPP11_OR_GREATER) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CPP11_OR_GREATER) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CPP11_OR_GREATER + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CPP11_OR_GREATER + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CPP11_OR_GREATER + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult->*expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct Equals : MatcherImpl { + Equals( std::string const& str ) : m_str( str ){} + Equals( Equals const& other ) : m_str( other.m_str ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_str == expr; + } + virtual std::string toString() const { + return "equals: \"" + m_str + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr ) : m_substr( substr ){} + StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr ) : m_substr( substr ){} + EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == expr.size() - m_substr.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + inline Impl::StdString::Equals Equals( const char* str ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); + } + inline Impl::StdString::Contains Contains( std::string const& substr ) { + return Impl::StdString::Contains( substr ); + } + inline Impl::StdString::Contains Contains( const char* substr ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { + if( startsWith( m_name, "*" ) ) { + m_name = m_name.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_name, "*" ) ) { + m_name = m_name.substr( 0, m_name.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_name == toLower( testCase.name ); + case WildcardAtStart: + return endsWith( toLower( testCase.name ), m_name ); + case WildcardAtEnd: + return startsWith( toLower( testCase.name ), m_name ); + case WildcardAtBothEnds: + return contains( toLower( testCase.name ), m_name ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual bool forceColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; + + std::ostream& cout(); + std::ostream& cerr(); +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + forceColour( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool forceColour; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( Catch::cout().rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( Catch::cout().rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( Catch::cout().rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + void setStreamBuf( std::streambuf* buf ) { + m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); + } + + void useStream( std::string const& streamName ) { + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; + } + + std::string getReporterName() const { return m_data.reporterName; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_os; } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual bool forceColour() const { return m_data.forceColour; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +# ifdef CATCH_CPP11_OR_GREATER + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // NOTE: std::auto_ptr is deprecated in c++11/c++0x +#if defined(__cplusplus) && __cplusplus > 199711L + typedef std::unique_ptr ArgAutoPtr; +#else + typedef std::auto_ptr ArgAutoPtr; +#endif + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output" ) + .bind( &ConfigData::forceColour ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CPP11_OR_GREATER + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CPP11_OR_GREATER + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CPP11_OR_GREATER + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CPP11_OR_GREATER + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + TrackedSection* acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + void enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + void leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + + }; + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + bool enterSection( std::string const& name ) { + TrackedSection* child = m_currentSection->acquireChild( name ); + if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) + return false; + + m_currentSection = child; + m_currentSection->enter(); + return true; + } + void leaveSection() { + m_currentSection->leave(); + m_currentSection = m_currentSection->getParent(); + assert( m_currentSection != NULL ); + m_completedASectionThisRun = true; + } + + bool currentSectionHasChildren() const { + return m_currentSection->hasChildren(); + } + bool isCompleted() const { + return m_testCase.runState() == TrackedSection::Completed; + } + + class Guard { + public: + Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { + m_tracker.enterTestCase(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + private: + Guard( Guard const& ); + void operator = ( Guard const& ); + TestCaseTracker& m_tracker; + }; + + private: + void enterTestCase() { + m_currentSection = &m_testCase; + m_completedASectionThisRun = false; + m_testCase.enter(); + } + void leaveTestCase() { + m_testCase.leave(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + m_context.setRunner( m_prevRunner ); + m_context.setConfig( NULL ); + m_context.setResultCapture( m_prevResultCapture ); + m_context.setConfig( m_prevConfig ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + m_unfinishedSections.clear(); + } + + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + Ptr m_prevConfig; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _buildNumber, + char const* const _branchName ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + buildNumber( _buildNumber ), + branchName( _branchName ) + {} + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const buildNumber; + char const* const branchName; + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + + TestSpec testSpec = m_config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector testCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + + int testsRunForGroup = 0; + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) { + testsRunForGroup++; + if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + + if( context.aborting() ) + break; + + totals += context.runTest( *it ); + m_testsAlreadyRun.insert( *it ); + } + } + std::vector skippedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + + for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); + it != itEnd; + ++it ) + m_reporter->skipTest( *it ); + + context.testGroupEnded( "all tests", totals, 1, 1 ); + return totals; + } + + private: + void openStream() { + // Open output file, if specified + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + m_config->setStreamBuf( m_ofs.rdbuf() ); + } + } + void makeReporter() { + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); + + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); + if( !m_reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + } + + private: + Ptr m_config; + std::ofstream m_ofs; + Ptr m_reporter; + std::set m_testsAlreadyRun; + }; + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " build " + << libraryVersion.buildNumber; + if( libraryVersion.branchName != std::string( "master" ) ) + Catch::cout() << " (" << libraryVersion.branchName << " branch)"; + Catch::cout() << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + std::srand( m_configData.rngSeed ); + + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector const& getAllNonHiddenTests() const { + return m_nonHiddenFunctions; + } + + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { + + for( std::vector::const_iterator it = m_functionsInOrder.begin(), + itEnd = m_functionsInOrder.end(); + it != itEnd; + ++it ) { + bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); + if( includeTest != negated ) + matchingTestCases.push_back( *it ); + } + sortTests( config, matchingTestCases ); + } + + private: + + static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + RandomNumberGenerator rng; + std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + } + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + std::string tryTranslators( std::vector::const_iterator it ) const { + if( it == m_translators.end() ) + return "Unknown exception"; + + try { + return (*it)->translate(); + } + catch(...) { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + Stream createStream( std::string const& streamName ) { + if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); + if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); + if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + void cleanUpContext() { + delete currentContext; + currentContext = NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + return (config && config->forceColour()) || isatty(STDOUT_FILENO) + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = isDebuggerActive() + ? NoColourImpl::instance() + : platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + tags( _tags ), + lineInfo( _lineInfo ), + properties( None ) + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); + lcaseTags.insert( lcaseTag ); + } + tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + // These numbers are maintained by a script + Version libraryVersion( 1, 1, 14, "develop" ); +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency((LARGE_INTEGER*)&hz); + QueryPerformanceCounter((LARGE_INTEGER*)&hzo); + } + uint64_t t; + QueryPerformanceCounter((LARGE_INTEGER*)&t); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + std::string unprintableString = "{?}"; + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + +//# ifndef CATCH_CPP11_OR_GREATER +// XmlWriter& operator = ( XmlWriter const& other ) { +// XmlWriter temp( other ); +// swap( temp ); +// return *this; +// } +//# else +// XmlWriter( XmlWriter const& ) = default; +// XmlWriter( XmlWriter && ) = default; +// XmlWriter& operator = ( XmlWriter const& ) = default; +// XmlWriter& operator = ( XmlWriter && ) = default; +//# endif +// +// void swap( XmlWriter& other ) { +// std::swap( m_tagIsOpen, other.m_tagIsOpen ); +// std::swap( m_needsNewline, other.m_needsNewline ); +// std::swap( m_tags, other.m_tags ); +// std::swap( m_indent, other.m_indent ); +// std::swap( m_os, other.m_os ); +// } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + void writeEncodedText( std::string const& text ) { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + {} + + virtual ~XmlReporter(); + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion.majorVersion << "." + << libraryVersion.minorVersion << " b" + << libraryVersion.buildNumber; + if( libraryVersion.branchName != std::string( "master" ) ) + stream << " (" << libraryVersion.branchName << ")"; + stream << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/extern/cmake/AndroidNdkModules.cmake b/extern/cmake/AndroidNdkModules.cmake new file mode 100644 index 00000000..253be15f --- /dev/null +++ b/extern/cmake/AndroidNdkModules.cmake @@ -0,0 +1,59 @@ +# Copyright (c) 2014, Pavel Rojtberg +# All rights reserved. +# +# 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. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# 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 HOLDER 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. + +macro(android_ndk_import_module_cpufeatures) + if(ANDROID) + include_directories(${ANDROID_NDK}/sources/android/cpufeatures) + add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c) + target_link_libraries(cpufeatures dl) + endif() +endmacro() + +macro(android_ndk_import_module_native_app_glue) + if(ANDROID) + include_directories(${ANDROID_NDK}/sources/android/native_app_glue) + #include_directories(${ANDROID_NDK}/platforms/android-21/arch-arm/usr/include/) + add_library(native_app_glue ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + target_link_libraries(native_app_glue log) + endif() +endmacro() + +macro(android_ndk_import_module_ndk_helper) + if(ANDROID) + android_ndk_import_module_cpufeatures() + android_ndk_import_module_native_app_glue() + + include_directories(${ANDROID_NDK}/sources/android/ndk_helper) + file(GLOB _NDK_HELPER_SRCS ${ANDROID_NDK}/sources/android/ndk_helper/*.cpp ${ANDROID_NDK}/sources/android/ndk_helper/gl3stub.c) + add_library(ndk_helper ${_NDK_HELPER_SRCS}) + target_link_libraries(ndk_helper log android EGL GLESv2 cpufeatures native_app_glue) + + unset(_NDK_HELPER_SRCS) + endif() +endmacro() diff --git a/extern/cmake/android.toolchain.cmake b/extern/cmake/android.toolchain.cmake new file mode 100644 index 00000000..07100ee3 --- /dev/null +++ b/extern/cmake/android.toolchain.cmake @@ -0,0 +1,1688 @@ +# Copyright (c) 2010-2011, Ethan Rublee +# Copyright (c) 2011-2014, Andrey Kamaev +# All rights reserved. +# +# 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. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# 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 HOLDER 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. + +# ------------------------------------------------------------------------------ +# Android CMake toolchain file, for use with the Android NDK r5-r10d +# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended). +# See home page: https://github.com/taka-no-me/android-cmake +# +# Usage Linux: +# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk +# $ mkdir build && cd build +# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. +# $ make -j8 +# +# Usage Windows: +# You need native port of make to build your project. +# Android NDK r7 (and newer) already has make.exe on board. +# For older NDK you have to install it separately. +# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm +# +# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk +# $ mkdir build && cd build +# $ cmake.exe -G"MinGW Makefiles" +# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake +# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" .. +# $ cmake.exe --build . +# +# +# Options (can be set as cmake parameters: -D=): +# ANDROID_NDK=/opt/android-ndk - path to the NDK root. +# Can be set as environment variable. Can be set only at first cmake run. +# +# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary +# Interface (ABI). This option nearly matches to the APP_ABI variable +# used by ndk-build tool from Android NDK. +# +# Possible targets are: +# "armeabi" - ARMv5TE based CPU with software floating point operations +# "armeabi-v7a" - ARMv7 based devices with hardware FPU instructions +# this ABI target is used by default +# "armeabi-v7a with NEON" - same as armeabi-v7a, but +# sets NEON as floating-point unit +# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but +# sets VFPV3 as floating-point unit (has 32 registers instead of 16) +# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP +# "x86" - IA-32 instruction set +# "mips" - MIPS32 instruction set +# +# 64-bit ABIs for NDK r10 and newer: +# "arm64-v8a" - ARMv8 AArch64 instruction set +# "x86_64" - Intel64 instruction set (r1) +# "mips64" - MIPS64 instruction set (r6) +# +# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for. +# Option is read-only when standalone toolchain is used. +# Note: building for "android-L" requires explicit configuration. +# +# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 - the name of compiler +# toolchain to be used. The list of possible values depends on the NDK +# version. For NDK r10c the possible values are: +# +# * aarch64-linux-android-4.9 +# * aarch64-linux-android-clang3.4 +# * aarch64-linux-android-clang3.5 +# * arm-linux-androideabi-4.6 +# * arm-linux-androideabi-4.8 +# * arm-linux-androideabi-4.9 (default) +# * arm-linux-androideabi-clang3.4 +# * arm-linux-androideabi-clang3.5 +# * mips64el-linux-android-4.9 +# * mips64el-linux-android-clang3.4 +# * mips64el-linux-android-clang3.5 +# * mipsel-linux-android-4.6 +# * mipsel-linux-android-4.8 +# * mipsel-linux-android-4.9 +# * mipsel-linux-android-clang3.4 +# * mipsel-linux-android-clang3.5 +# * x86-4.6 +# * x86-4.8 +# * x86-4.9 +# * x86-clang3.4 +# * x86-clang3.5 +# * x86_64-4.9 +# * x86_64-clang3.4 +# * x86_64-clang3.5 +# +# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions +# instead of Thumb. Is not available for "armeabi-v6 with VFP" +# (is forced to be ON) ABI. +# +# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker +# errors even if they are not used. +# +# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared +# libraries. Automatically turned for NDK r5x and r6x due to GLESv2 +# problems. +# +# ANDROID_STL=gnustl_static - specify the runtime to use. +# +# Possible values are: +# none -> Do not configure the runtime. +# system -> Use the default minimal system C++ runtime library. +# Implies -fno-rtti -fno-exceptions. +# Is not available for standalone toolchain. +# system_re -> Use the default minimal system C++ runtime library. +# Implies -frtti -fexceptions. +# Is not available for standalone toolchain. +# gabi++_static -> Use the GAbi++ runtime as a static library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# gabi++_shared -> Use the GAbi++ runtime as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_static -> Use the STLport runtime as a static library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_shared -> Use the STLport runtime as a shared library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# gnustl_static -> Use the GNU STL as a static library. +# Implies -frtti -fexceptions. +# gnustl_shared -> Use the GNU STL as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7b and newer. +# Silently degrades to gnustl_static if not available. +# +# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on +# chosen runtime. If disabled, then the user is responsible for settings +# these options. +# +# What?: +# android-cmake toolchain searches for NDK/toolchain in the following order: +# ANDROID_NDK - cmake parameter +# ANDROID_NDK - environment variable +# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter +# ANDROID_STANDALONE_TOOLCHAIN - environment variable +# ANDROID_NDK - default locations +# ANDROID_STANDALONE_TOOLCHAIN - default locations +# +# Make sure to do the following in your scripts: +# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" ) +# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" ) +# The flags will be prepopulated with critical flags, so don't loose them. +# Also be aware that toolchain also sets configuration-specific compiler +# flags and linker flags. +# +# ANDROID and BUILD_ANDROID will be set to true, you may test any of these +# variables to make necessary Android-specific configuration changes. +# +# Also ARMEABI or ARMEABI_V7A or X86 or MIPS or ARM64_V8A or X86_64 or MIPS64 +# will be set true, mutually exclusive. NEON option will be set true +# if VFP is set to NEON. +# +# ------------------------------------------------------------------------------ + +cmake_minimum_required( VERSION 2.6.3 ) + +if( DEFINED CMAKE_CROSSCOMPILING ) + # subsequent toolchain loading is not really needed + return() +endif() + +if( CMAKE_TOOLCHAIN_FILE ) + # touch toolchain variable to suppress "unused variable" warning +endif() + +# inherit settings in recursive loads +get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) +if( _CMAKE_IN_TRY_COMPILE ) + include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL ) +endif() + +# this one is important +if( CMAKE_VERSION VERSION_GREATER "3.0.99" ) + set( CMAKE_SYSTEM_NAME Android ) +else() + set( CMAKE_SYSTEM_NAME Linux ) +endif() + +# this one not so much +set( CMAKE_SYSTEM_VERSION 1 ) + +# rpath makes low sense for Android +set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" ) +set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) + +# NDK search paths +set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) +if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS ) + if( CMAKE_HOST_WIN32 ) + file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" ) + else() + file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" ) + endif() +endif() +if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) +endif() + +# known ABIs +set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" ) +set( ANDROID_SUPPORTED_ABIS_arm64 "arm64-v8a" ) +set( ANDROID_SUPPORTED_ABIS_x86 "x86" ) +set( ANDROID_SUPPORTED_ABIS_x86_64 "x86_64" ) +set( ANDROID_SUPPORTED_ABIS_mips "mips" ) +set( ANDROID_SUPPORTED_ABIS_mips64 "mips64" ) + +# API level defaults +set( ANDROID_DEFAULT_NDK_API_LEVEL 8 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_arm64 21 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86_64 21 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 ) + + +macro( __LIST_FILTER listvar regex ) + if( ${listvar} ) + foreach( __val ${${listvar}} ) + if( __val MATCHES "${regex}" ) + list( REMOVE_ITEM ${listvar} "${__val}" ) + endif() + endforeach() + endif() +endmacro() + +macro( __INIT_VARIABLE var_name ) + set( __test_path 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "PATH" ) + set( __test_path 1 ) + break() + endif() + endforeach() + + if( __test_path AND NOT EXISTS "${${var_name}}" ) + unset( ${var_name} CACHE ) + endif() + + if( " ${${var_name}}" STREQUAL " " ) + set( __values 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "VALUES" ) + set( __values 1 ) + elseif( NOT __var STREQUAL "PATH" ) + if( __var MATCHES "^ENV_.*$" ) + string( REPLACE "ENV_" "" __var "${__var}" ) + set( __value "$ENV{${__var}}" ) + elseif( DEFINED ${__var} ) + set( __value "${${__var}}" ) + elseif( __values ) + set( __value "${__var}" ) + else() + set( __value "" ) + endif() + + if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") ) + set( ${var_name} "${__value}" ) + break() + endif() + endif() + endforeach() + unset( __value ) + unset( __values ) + endif() + + if( __test_path ) + file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) + endif() + unset( __test_path ) +endmacro() + +macro( __DETECT_NATIVE_API_LEVEL _var _path ) + set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) + file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) + if( NOT __apiFileContent ) + message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) + endif() + string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) + unset( __apiFileContent ) + unset( __ndkApiLevelRegex ) +endmacro() + +macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) + if( EXISTS "${_root}" ) + file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) + __LIST_FILTER( __gccExePath "^[.].*" ) + list( LENGTH __gccExePath __gccExePathsCount ) + if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Could not determine machine name for compiler from ${_root}" ) + set( ${_var} "" ) + else() + get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) + string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) + endif() + unset( __gccExePath ) + unset( __gccExePathsCount ) + unset( __gccExeName ) + else() + set( ${_var} "" ) + endif() +endmacro() + + +# fight against cygwin +set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools") +mark_as_advanced( ANDROID_FORBID_SYGWIN ) +if( ANDROID_FORBID_SYGWIN ) + if( CYGWIN ) + message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." ) + endif() + + if( CMAKE_HOST_WIN32 ) + # remove cygwin from PATH + set( __new_path "$ENV{PATH}") + __LIST_FILTER( __new_path "cygwin" ) + set(ENV{PATH} "${__new_path}") + unset(__new_path) + endif() +endif() + + +# detect current host platform +if( NOT DEFINED ANDROID_NDK_HOST_X64 AND (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64" OR CMAKE_HOST_APPLE) ) + set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" ) + mark_as_advanced( ANDROID_NDK_HOST_X64 ) +endif() + +set( TOOL_OS_SUFFIX "" ) +if( CMAKE_HOST_APPLE ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" ) +elseif( CMAKE_HOST_WIN32 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" ) + set( TOOL_OS_SUFFIX ".exe" ) +elseif( CMAKE_HOST_UNIX ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" ) +else() + message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" ) +endif() + +if( NOT ANDROID_NDK_HOST_X64 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) +endif() + +# see if we have path to Android NDK +if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN ) + __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) +endif() +if( NOT ANDROID_NDK ) + # see if we have path to Android standalone toolchain + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) + + if( NOT ANDROID_STANDALONE_TOOLCHAIN ) + #try to find Android NDK in one of the the default locations + set( __ndkSearchPaths ) + foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) + foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) + list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" ) + endforeach() + endforeach() + __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) + unset( __ndkSearchPaths ) + + if( ANDROID_NDK ) + message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) + message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) + else() + #try to find Android standalone toolchain in one of the the default locations + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + + if( ANDROID_STANDALONE_TOOLCHAIN ) + message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" ) + message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" ) + endif( ANDROID_STANDALONE_TOOLCHAIN ) + endif( ANDROID_NDK ) + endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) +endif( NOT ANDROID_NDK ) + +# remember found paths +if( ANDROID_NDK ) + get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) + set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) + set( BUILD_WITH_ANDROID_NDK True ) + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) + file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX "r[0-9]+[a-z]?" ) + string( REGEX MATCH "r([0-9]+)([a-z]?)" ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + else() + set( ANDROID_NDK_RELEASE "r1x" ) + set( ANDROID_NDK_RELEASE_FULL "unreleased" ) + endif() + string( REGEX REPLACE "r([0-9]+)([a-z]?)" "\\1*1000" ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE}" ) + string( FIND " abcdefghijklmnopqastuvwxyz" "${CMAKE_MATCH_2}" __ndkReleaseLetterNum ) + math( EXPR ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE_NUM}+${__ndkReleaseLetterNum}" ) +elseif( ANDROID_STANDALONE_TOOLCHAIN ) + get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) + # try to detect change + if( CMAKE_AR ) + string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath ) + if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN ) + message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." ) + endif() + unset( __androidStandaloneToolchainPreviousPath ) + unset( __length ) + endif() + set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE ) + set( BUILD_WITH_STANDALONE_TOOLCHAIN True ) +else() + list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH) + message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain. + You should either set an environment variable: + export ANDROID_NDK=~/my-android-ndk + or + export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain + or put the toolchain or NDK in the default path: + sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk + sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) +endif() + +# android NDK layout +if( BUILD_WITH_ANDROID_NDK ) + if( NOT DEFINED ANDROID_NDK_LAYOUT ) + # try to automatically detect the layout + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") + set( ANDROID_NDK_LAYOUT "RELEASE" ) + elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) + set( ANDROID_NDK_LAYOUT "LINARO" ) + elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) + set( ANDROID_NDK_LAYOUT "ANDROID" ) + endif() + endif() + set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) + mark_as_advanced( ANDROID_NDK_LAYOUT ) + if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) + endif() + get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) + + # try to detect change of NDK + if( CMAKE_AR ) + string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) + if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) + message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. + " ) + endif() + unset( __androidNdkPreviousPath ) + unset( __length ) + endif() +endif() + + +# get all the details about standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) + set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + set( __availableToolchains "standalone" ) + __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" ) + if( NOT __availableToolchainMachines ) + message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." ) + endif() + if( __availableToolchainMachines MATCHES x86_64 ) + set( __availableToolchainArchs "x86_64" ) + elseif( __availableToolchainMachines MATCHES i686 ) + set( __availableToolchainArchs "x86" ) + elseif( __availableToolchainMachines MATCHES aarch64 ) + set( __availableToolchainArchs "arm64" ) + elseif( __availableToolchainMachines MATCHES arm ) + set( __availableToolchainArchs "arm" ) + elseif( __availableToolchainMachines MATCHES mips64el ) + set( __availableToolchainArchs "mips64" ) + elseif( __availableToolchainMachines MATCHES mipsel ) + set( __availableToolchainArchs "mips" ) + endif() + execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion + OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" ) + if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" ) + list( APPEND __availableToolchains "standalone-clang" ) + list( APPEND __availableToolchainMachines ${__availableToolchainMachines} ) + list( APPEND __availableToolchainArchs ${__availableToolchainArchs} ) + list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} ) + endif() +endif() + +macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) + foreach( __toolchain ${${__availableToolchainsLst}} ) + if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) + SET( __toolchainVersionRegex "^TOOLCHAIN_VERSION[\t ]+:=[\t ]+(.*)$" ) + FILE( STRINGS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}/setup.mk" __toolchainVersionStr REGEX "${__toolchainVersionRegex}" ) + if( __toolchainVersionStr ) + string( REGEX REPLACE "${__toolchainVersionRegex}" "\\1" __toolchainVersionStr "${__toolchainVersionStr}" ) + string( REGEX REPLACE "-clang3[.][0-9]$" "-${__toolchainVersionStr}" __gcc_toolchain "${__toolchain}" ) + else() + string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) + endif() + unset( __toolchainVersionStr ) + unset( __toolchainVersionRegex ) + else() + set( __gcc_toolchain "${__toolchain}" ) + endif() + __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) + if( __machine ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) + if( __machine MATCHES x86_64 ) + set( __arch "x86_64" ) + elseif( __machine MATCHES i686 ) + set( __arch "x86" ) + elseif( __machine MATCHES aarch64 ) + set( __arch "arm64" ) + elseif( __machine MATCHES arm ) + set( __arch "arm" ) + elseif( __machine MATCHES mips64el ) + set( __arch "mips64" ) + elseif( __machine MATCHES mipsel ) + set( __arch "mips" ) + else() + set( __arch "" ) + endif() + #message("machine: !${__machine}!\narch: !${__arch}!\nversion: !${__version}!\ntoolchain: !${__toolchain}!\n") + if (__arch) + list( APPEND __availableToolchainMachines "${__machine}" ) + list( APPEND __availableToolchainArchs "${__arch}" ) + list( APPEND __availableToolchainCompilerVersions "${__version}" ) + list( APPEND ${__availableToolchainsVar} "${__toolchain}" ) + endif() + endif() + unset( __gcc_toolchain ) + endforeach() +endmacro() + +# get all the details about NDK +if( BUILD_WITH_ANDROID_NDK ) + file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" ) + string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" ) + set( __availableToolchains "" ) + set( __availableToolchainMachines "" ) + set( __availableToolchainArchs "" ) + set( __availableToolchainCompilerVersions "" ) + if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) + # do not go through all toolchains if we know the name + set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) + if( __availableToolchains ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) + if( __availableToolchains ) + list(SORT __availableToolchainsLst) # we need clang to go after gcc + endif() + __LIST_FILTER( __availableToolchainsLst "^[.]" ) + __LIST_FILTER( __availableToolchainsLst "llvm" ) + __LIST_FILTER( __availableToolchainsLst "renderscript" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) + if( __availableToolchains ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." ) + endif() +endif() + +# build list of available ABIs +set( ANDROID_SUPPORTED_ABIS "" ) +set( __uniqToolchainArchNames ${__availableToolchainArchs} ) +list( REMOVE_DUPLICATES __uniqToolchainArchNames ) +list( SORT __uniqToolchainArchNames ) +foreach( __arch ${__uniqToolchainArchNames} ) + list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} ) +endforeach() +unset( __uniqToolchainArchNames ) +if( NOT ANDROID_SUPPORTED_ABIS ) + message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." ) +endif() + +# choose target ABI +__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} ) +# verify that target ABI is supported +list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) +if( __androidAbiIdx EQUAL -1 ) + string( REPLACE ";" "\", \"" PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" ) + message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain. + Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\" + " ) +endif() +unset( __androidAbiIdx ) + +# set target ABI options +if( ANDROID_ABI STREQUAL "x86" ) + set( X86 true ) + set( ANDROID_NDK_ABI_NAME "x86" ) + set( ANDROID_ARCH_NAME "x86" ) + set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "i686" ) +elseif( ANDROID_ABI STREQUAL "x86_64" ) + set( X86 true ) + set( X86_64 true ) + set( ANDROID_NDK_ABI_NAME "x86_64" ) + set( ANDROID_ARCH_NAME "x86_64" ) + set( CMAKE_SYSTEM_PROCESSOR "x86_64" ) + set( ANDROID_LLVM_TRIPLE "x86_64-none-linux-android" ) +elseif( ANDROID_ABI STREQUAL "mips64" ) + set( MIPS64 true ) + set( ANDROID_NDK_ABI_NAME "mips64" ) + set( ANDROID_ARCH_NAME "mips64" ) + set( ANDROID_LLVM_TRIPLE "mips64el-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips64" ) +elseif( ANDROID_ABI STREQUAL "mips" ) + set( MIPS true ) + set( ANDROID_NDK_ABI_NAME "mips" ) + set( ANDROID_ARCH_NAME "mips" ) + set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips" ) +elseif( ANDROID_ABI STREQUAL "arm64-v8a" ) + set( ARM64_V8A true ) + set( ANDROID_NDK_ABI_NAME "arm64-v8a" ) + set( ANDROID_ARCH_NAME "arm64" ) + set( ANDROID_LLVM_TRIPLE "aarch64-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "aarch64" ) + set( VFPV3 true ) + set( NEON true ) +elseif( ANDROID_ABI STREQUAL "armeabi" ) + set( ARMEABI true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv5te" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" ) + set( ARMEABI_V6 true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv6" ) + # need always fallback to older platform + set( ARMEABI true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a") + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) + set( NEON true ) +else() + message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." ) +endif() + +if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" ) + # really dirty hack + # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run... + file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" ) +endif() + +if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) + __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF ) + set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) + mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) +else() + unset( ANDROID_FORCE_ARM_BUILD CACHE ) +endif() + +# choose toolchain +if( ANDROID_TOOLCHAIN_NAME ) + list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx ) + if( __toolchainIdx EQUAL -1 ) + list( SORT __availableToolchains ) + string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" ) + set( toolchains_list " * ${toolchains_list}") + message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain. +To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" ) + endif() + list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch ) + if( NOT __toolchainArch STREQUAL ANDROID_ARCH_NAME ) + message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." ) + endif() +else() + set( __toolchainIdx -1 ) + set( __applicableToolchains "" ) + set( __toolchainMaxVersion "0.0.0" ) + list( LENGTH __availableToolchains __availableToolchainsCount ) + math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" ) + foreach( __idx RANGE ${__availableToolchainsCount} ) + list( GET __availableToolchainArchs ${__idx} __toolchainArch ) + if( __toolchainArch STREQUAL ANDROID_ARCH_NAME ) + list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) + string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") + if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) + set( __toolchainMaxVersion "${__toolchainVersion}" ) + set( __toolchainIdx ${__idx} ) + endif() + endif() + endforeach() + unset( __availableToolchainsCount ) + unset( __toolchainMaxVersion ) + unset( __toolchainVersion ) +endif() +unset( __toolchainArch ) +if( __toolchainIdx EQUAL -1 ) + message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." ) +endif() +list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME ) +list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME ) +list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION ) + +unset( __toolchainIdx ) +unset( __availableToolchains ) +unset( __availableToolchainMachines ) +unset( __availableToolchainArchs ) +unset( __availableToolchainCompilerVersions ) + +# choose native API level +__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL ) +string( REPLACE "android-" "" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" ) +string( STRIP "${ANDROID_NATIVE_API_LEVEL}" ANDROID_NATIVE_API_LEVEL ) +# adjust API level +set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} ) +foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + if( (__level LESS ANDROID_NATIVE_API_LEVEL OR __level STREQUAL ANDROID_NATIVE_API_LEVEL) AND NOT __level LESS __real_api_level ) + set( __real_api_level ${__level} ) + endif() +endforeach() +if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL STREQUAL __real_api_level ) + message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'") + set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} ) +endif() +unset(__real_api_level) +# validate +list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx ) +if( __levelIdx EQUAL -1 ) + message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." ) +else() + if( BUILD_WITH_ANDROID_NDK ) + __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" ) + if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL AND NOT __realApiLevel GREATER 9000 ) + message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." ) + endif() + unset( __realApiLevel ) + endif() + set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) + set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} ) + if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) + set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + endif() +endif() +unset( __levelIdx ) + + +# remember target ABI +set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE ) +if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME} ) + set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME}} ) +endif() + + +# runtime choice (STL, rtti, exceptions) +if( NOT ANDROID_STL ) + set( ANDROID_STL gnustl_static ) +endif() +set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) +set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) +mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) + +if( BUILD_WITH_ANDROID_NDK ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + system -> Use the default minimal system C++ runtime library. + system_re -> Same as system but with rtti and exceptions. + gabi++_static -> Use the GAbi++ runtime as a static library. + gabi++_shared -> Use the GAbi++ runtime as a shared library. + stlport_static -> Use the STLport runtime as a static library. + stlport_shared -> Use the STLport runtime as a shared library. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +endif() + +unset( ANDROID_RTTI ) +unset( ANDROID_EXCEPTIONS ) +unset( ANDROID_STL_INCLUDE_DIRS ) +unset( __libstl ) +unset( __libsupcxx ) + +if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" ) + message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf). +You are strongly recommended to switch to another NDK release. +" ) +endif() + +if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) + message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header: +See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2 + diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + index 5e28c64..65892a1 100644 + --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h + +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + @@ -51,7 +51,11 @@ typedef long int ssize_t; + #endif + #ifndef _PTRDIFF_T + #define _PTRDIFF_T + -typedef long ptrdiff_t; + +# ifdef __ANDROID__ + + typedef int ptrdiff_t; + +# else + + typedef long ptrdiff_t; + +# endif + #endif +" ) +endif() + + +# setup paths and STL for standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" ) + + if( NOT ANDROID_STL STREQUAL "none" ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/include/c++/${ANDROID_COMPILER_VERSION}" ) + if( NOT EXISTS "${ANDROID_STL_INCLUDE_DIRS}" ) + # old location ( pre r8c ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" ) + endif() + if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" ) + else() + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" ) + endif() + # always search static GNU STL to get the location of libsupc++.a + if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" ) + elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" ) + endif() + if( __libstl ) + set( __libsupcxx "${__libstl}/libsupc++.a" ) + set( __libstl "${__libstl}/libstdc++.a" ) + endif() + if( NOT EXISTS "${__libsupcxx}" ) + message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain. + Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c. + You need to either upgrade to newer NDK or manually copy + $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a + to + ${__libsupcxx} + " ) + endif() + if( ANDROID_STL STREQUAL "gnustl_shared" ) + if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + endif() + endif() + endif() +endif() + +# clang +if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) + set( ANDROID_COMPILER_IS_CLANG 1 ) + execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}") +elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) + string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") + string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-${ANDROID_COMPILER_VERSION}" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) + message( FATAL_ERROR "Could not find the Clang compiler driver" ) + endif() + set( ANDROID_COMPILER_IS_CLANG 1 ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) +else() + set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + unset( ANDROID_COMPILER_IS_CLANG CACHE ) +endif() + +string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" ) +if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" ) + set( _clang_name "clang" ) +endif() + + +# setup paths and STL for NDK +if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) + + if( ANDROID_STL STREQUAL "none" ) + # do nothing + elseif( ANDROID_STL STREQUAL "system" ) + set( ANDROID_RTTI OFF ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL STREQUAL "system_re" ) + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL MATCHES "gabi" ) + if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + message( FATAL_ERROR "gabi++ is not awailable in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") + endif() + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" ) + elseif( ANDROID_STL MATCHES "stlport" ) + if( NOT ANDROID_NDK_RELEASE_NUM LESS 8004 ) # before r8d + set( ANDROID_EXCEPTIONS ON ) + else() + set( ANDROID_EXCEPTIONS OFF ) + endif() + if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + set( ANDROID_RTTI OFF ) + else() + set( ANDROID_RTTI ON ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" ) + elseif( ANDROID_STL MATCHES "gnustl" ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_RTTI ON ) + if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" ) + # gnustl binary for 4.7 compiler is buggy :( + # TODO: look for right fix + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" ) + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + endif() + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" ) + if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + else() + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) + endif() + else() + message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) + endif() + # find libsupc++.a - rtti & exceptions + if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer + if( NOT EXISTS "${__libsupcxx}" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 + endif() + if( NOT EXISTS "${__libsupcxx}" ) # before r7 + if( ARMEABI_V7A ) + if( ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" ) + endif() + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" ) + endif() + endif() + if( NOT EXISTS "${__libsupcxx}") + message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.") + endif() + endif() +endif() + + +# case of shared STL linkage +if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl ) + string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" ) + # TODO: check if .so file exists before the renaming +endif() + + +# ccache support +__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE ) +if( _ndk_ccache ) + if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE ) + unset( NDK_CCACHE CACHE ) + endif() + find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary") +else() + unset( NDK_CCACHE CACHE ) +endif() +unset( _ndk_ccache ) + + +# setup the cross-compiler +if( NOT CMAKE_C_COMPILER ) + if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) + set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + endif() + else() + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" ) + set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" ) + endif() + endif() + set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) + set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) + set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) + set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) + set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" ) + set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" ) +endif() + +set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" ) +if( CMAKE_VERSION VERSION_LESS 2.8.5 ) + set( CMAKE_ASM_COMPILER_ARG1 "-c" ) +endif() +if( APPLE ) + find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool ) + if( NOT CMAKE_INSTALL_NAME_TOOL ) + message( FATAL_ERROR "Could not find install_name_tool, please check your installation." ) + endif() + mark_as_advanced( CMAKE_INSTALL_NAME_TOOL ) +endif() + +# Force set compilers because standard identification works badly for us +include( CMakeForceCompiler ) +CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ID Clang ) +endif() +set( CMAKE_C_PLATFORM_ID Linux ) +if( X86_64 OR MIPS64 OR ARM64_V8A ) + set( CMAKE_C_SIZEOF_DATA_PTR 8 ) +else() + set( CMAKE_C_SIZEOF_DATA_PTR 4 ) +endif() +set( CMAKE_C_HAS_ISYSROOT 1 ) +set( CMAKE_C_COMPILER_ABI ELF ) +CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_CXX_COMPILER_ID Clang) +endif() +set( CMAKE_CXX_PLATFORM_ID Linux ) +set( CMAKE_CXX_SIZEOF_DATA_PTR ${CMAKE_C_SIZEOF_DATA_PTR} ) +set( CMAKE_CXX_HAS_ISYSROOT 1 ) +set( CMAKE_CXX_COMPILER_ABI ELF ) +set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C ) +# force ASM compiler (required for CMake < 2.8.5) +set( CMAKE_ASM_COMPILER_ID_RUN TRUE ) +set( CMAKE_ASM_COMPILER_ID GNU ) +set( CMAKE_ASM_COMPILER_WORKS TRUE ) +set( CMAKE_ASM_COMPILER_FORCED TRUE ) +set( CMAKE_COMPILER_IS_GNUASM 1) +set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) + +foreach( lang C CXX ASM ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} ) + else() + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} ) + endif() +endforeach() + +# flags and definitions +remove_definitions( -DANDROID ) +add_definitions( -DANDROID ) + +if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) + if( CMAKE_HOST_WIN32 ) + # try to convert path to 8.3 form + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) + execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" + OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE __result ERROR_QUIET ) + if( __result EQUAL 0 ) + file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) + else() + set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) + endif() + else() + set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) + endif() + if( NOT _CMAKE_IN_TRY_COMPILE ) + # quotes can break try_compile and compiler identification + message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") + endif() +else() + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) +endif() + +# NDK flags +if (ARM64_V8A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif( ARMEABI OR ARMEABI_V7A) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" ) + endif() + else() + # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI + set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + endif() +elseif( X86 OR X86_64 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) +elseif( MIPS OR MIPS64 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif() + set( ANDROID_CXX_FLAGS_RELEASE "" ) + set( ANDROID_CXX_FLAGS_DEBUG "" ) +endif() + +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries + +if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" ) +endif() + +if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/ +endif() + +# ABI-specific flags +if( ARMEABI_V7A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" ) + if( NEON ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" ) + elseif( VFPV3 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" ) + endif() +elseif( ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2 +elseif( ARMEABI ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) +endif() + +if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +else() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +endif() + +# STL +if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) + if( EXISTS "${__libstl}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) + endif() + if( EXISTS "${__libsupcxx}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + # C objects: + set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_C_LINK_EXECUTABLE " -o " ) + set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + endif() + if( ANDROID_STL MATCHES "gnustl" ) + if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) + set( ANDROID_LIBM_PATH -lm ) + endif() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) + endif() +endif() + +# variables controlling optional build flags +if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. + # So this flag option is required for all projects using OpenGL from native. + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) +else() + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) +endif() +__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) +__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) +__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) +__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) +__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) + +set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) +set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) +set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) +set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) +mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) + +# linker flags +set( ANDROID_LINKER_FLAGS "" ) + +if( ARMEABI_V7A ) + # this is *required* to use the following linker flags that routes around + # a CPU bug in some Cortex-A8 implementations: + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) +endif() + +if( ANDROID_NO_UNDEFINED ) + if( MIPS ) + # there is some sysroot-related problem in mips linker... + if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) + endif() + else() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() +endif() + +if( ANDROID_SO_UNDEFINED ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) +endif() + +if( ANDROID_FUNCTION_LEVEL_LINKING ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) +endif() + +if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) + if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE_NUM GREATER 8002) AND (ARMEABI OR ARMEABI_V7A OR X86) ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) + elseif( ANDROID_NDK_RELEASE_NUM GREATER 8002 ) # after r8b + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) + elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 + On Linux and OS X host platform you can workaround this problem using gold linker (default). + Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. +" ) + endif() +endif() # version 4.6 + +if( ANDROID_NOEXECSTACK ) + if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) + endif() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) +endif() + +if( ANDROID_RELRO ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) +endif() + +if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} -Qunused-arguments ${ANDROID_CXX_FLAGS}" ) + if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) + endif() +endif() + +# cache flags +set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) +set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) +set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) +set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) +set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) +set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) +set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" ) +set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" ) +set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) + +# put flags to cache (for debug purpose only) +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) +set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) +set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) +set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) + +# finish flags +set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) +set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) +set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) +set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) +set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) +set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) +set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) +set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) +set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) + +if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) + set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) + set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) + set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) +endif() + +# pie/pic +if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) + set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") +else() + set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) + set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) +endif() + +# configure rtti +if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_RTTI ) + set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) + endif() +endif() + +# configure exceptios +if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_EXCEPTIONS ) + set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) + endif() +endif() + +# global includes and link directories +include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) +get_filename_component(__android_install_path "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ABSOLUTE) # avoid CMP0015 policy warning +link_directories( "${__android_install_path}" ) + +# detect if need link crtbegin_so.o explicitly +if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) + set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) + string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "-shared" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) + string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + separate_arguments( __cmd ) + foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) + if( ${__var} ) + set( __tmp "${${__var}}" ) + separate_arguments( __tmp ) + string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") + endif() + endforeach() + string( REPLACE "'" "" __cmd "${__cmd}" ) + string( REPLACE "\"" "" __cmd "${__cmd}" ) + execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) + if( __cmd_result EQUAL 0 ) + set( ANDROID_EXPLICIT_CRT_LINK ON ) + else() + set( ANDROID_EXPLICIT_CRT_LINK OFF ) + endif() +endif() + +if( ANDROID_EXPLICIT_CRT_LINK ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) +endif() + +# setup output directories +set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) + +if( DEFINED LIBRARY_OUTPUT_PATH_ROOT + OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" + OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) + set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) + if( NOT _CMAKE_IN_TRY_COMPILE ) + if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) + else() + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) + endif() + set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) + endif() +endif() + +# copy shaed stl library to build directory +if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) + get_filename_component( __libstlname "${__libstl}" NAME ) + execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) + if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") + message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) + endif() + unset( __fileCopyProcess ) + unset( __libstlname ) +endif() + + +# set these global flags for cmake client scripts to change behavior +set( ANDROID True ) +set( BUILD_ANDROID True ) + +# where is the target environment +set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) + +# only search for libraries and includes in the ndk toolchain +set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) + + +# macro to find packages on the host OS +macro( find_host_package ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_package( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# macro to find programs on the host OS +macro( find_host_program ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_program( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# export toolchain settings for the try_compile() command +if( NOT _CMAKE_IN_TRY_COMPILE ) + set( __toolchain_config "") + foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN + ANDROID_NDK_HOST_X64 + ANDROID_NDK + ANDROID_NDK_LAYOUT + ANDROID_STANDALONE_TOOLCHAIN + ANDROID_TOOLCHAIN_NAME + ANDROID_ABI + ANDROID_NATIVE_API_LEVEL + ANDROID_STL + ANDROID_STL_FORCE_FEATURES + ANDROID_FORCE_ARM_BUILD + ANDROID_NO_UNDEFINED + ANDROID_SO_UNDEFINED + ANDROID_FUNCTION_LEVEL_LINKING + ANDROID_GOLD_LINKER + ANDROID_NOEXECSTACK + ANDROID_RELRO + ANDROID_LIBM_PATH + ANDROID_EXPLICIT_CRT_LINK + ANDROID_APP_PIE + ) + if( DEFINED ${__var} ) + if( ${__var} MATCHES " ") + set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) + else() + set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) + endif() + endif() + endforeach() + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) + unset( __toolchain_config ) +endif() + + +# force cmake to produce / instead of \ in build commands for Ninja generator +if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) + # it is a bad hack after all + # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW + set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW + set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion + enable_language( C ) + enable_language( CXX ) + # unset( CMAKE_COMPILER_IS_MINGW ) # can't unset because CMake does not convert back-slashes in response files without it + unset( MINGW ) +endif() + + +# Variables controlling behavior or set by cmake toolchain: +# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" +# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) +# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none +# ANDROID_FORBID_SYGWIN : ON/OFF +# ANDROID_NO_UNDEFINED : ON/OFF +# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) +# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF +# ANDROID_GOLD_LINKER : ON/OFF +# ANDROID_NOEXECSTACK : ON/OFF +# ANDROID_RELRO : ON/OFF +# ANDROID_FORCE_ARM_BUILD : ON/OFF +# ANDROID_STL_FORCE_FEATURES : ON/OFF +# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` +# Can be set only at the first run: +# ANDROID_NDK : path to your NDK install +# NDK_CCACHE : path to your ccache executable +# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain +# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) +# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) +# LIBRARY_OUTPUT_PATH_ROOT : +# ANDROID_STANDALONE_TOOLCHAIN +# +# Primary read-only variables: +# ANDROID : always TRUE +# ARMEABI : TRUE for arm v6 and older devices +# ARMEABI_V6 : TRUE for arm v6 +# ARMEABI_V7A : TRUE for arm v7a +# ARM64_V8A : TRUE for arm64-v8a +# NEON : TRUE if NEON unit is enabled +# VFPV3 : TRUE if VFP version 3 is enabled +# X86 : TRUE if configured for x86 +# X86_64 : TRUE if configured for x86_64 +# MIPS : TRUE if configured for mips +# MIPS64 : TRUE if configured for mips64 +# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used +# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used +# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform +# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI +# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK +# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) +# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI +# ANDROID_SYSROOT : path to the compiler sysroot +# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform +# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used +# +# Secondary (less stable) read-only variables: +# ANDROID_COMPILER_VERSION : GCC version used (not Clang version) +# ANDROID_CLANG_VERSION : version of clang compiler if clang is used +# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform +# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI +# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" +# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) +# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools +# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK +# ANDROID_STL_INCLUDE_DIRS : stl include paths +# ANDROID_RTTI : if rtti is enabled by the runtime +# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime +# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used +# +# Defaults: +# ANDROID_DEFAULT_NDK_API_LEVEL +# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} +# ANDROID_NDK_SEARCH_PATHS +# ANDROID_SUPPORTED_ABIS_${ARCH} +# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/extern/cmake/iOS.cmake b/extern/cmake/iOS.cmake new file mode 100644 index 00000000..f85d0350 --- /dev/null +++ b/extern/cmake/iOS.cmake @@ -0,0 +1,200 @@ +# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake +# files which are included with CMake 2.8.4 +# It has been altered for iOS development + +# Options: +# +# IOS_PLATFORM = OS (default) or SIMULATOR +# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders +# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch. +# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. +# +# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder +# By default this location is automatcially chosen based on the IOS_PLATFORM value above. +# If set manually, it will override the default location and force the user of a particular Developer Platform +# +# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder +# By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value. +# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path. +# If set manually, this will force the use of a specific SDK version + +# Macros: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE) +# A convenience macro for setting xcode specific properties on targets +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1") +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the iOS environment. +# Thanks to the android-cmake project for providing the command + +# Standard settings +set (CMAKE_SYSTEM_NAME Darwin) +set (CMAKE_SYSTEM_VERSION 1) +set (UNIX True) +set (APPLE True) +set (IOS True) + +# Required as of cmake 2.8.10 +set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) + +# Determine the cmake host system version so we know where to find the iOS SDKs +find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) +if (CMAKE_UNAME) + exec_program(uname ARGS -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) + string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") +endif (CMAKE_UNAME) + +# Force the compilers to gcc for iOS +include (CMakeForceCompiler) +CMAKE_FORCE_C_COMPILER (/usr/bin/clang AppleClang) +CMAKE_FORCE_CXX_COMPILER (/usr/bin/clang++ AppleClang) +set(CMAKE_AR ar CACHE FILEPATH "" FORCE) + +# Skip the platform compiler checks for cross compiling +set (CMAKE_CXX_COMPILER_WORKS TRUE) +set (CMAKE_C_COMPILER_WORKS TRUE) + +# All iOS/Darwin specific settings - some may be redundant +set (CMAKE_SHARED_LIBRARY_PREFIX "lib") +set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set (CMAKE_SHARED_MODULE_PREFIX "lib") +set (CMAKE_SHARED_MODULE_SUFFIX ".so") +set (CMAKE_MODULE_EXISTS 1) +set (CMAKE_DL_LIBS "") + +set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +# Hidden visibilty is required for cxx on iOS +set (CMAKE_C_FLAGS_INIT "") +set (CMAKE_CXX_FLAGS_INIT "-fvisibility=hidden -fvisibility-inlines-hidden -isysroot ${CMAKE_OSX_SYSROOT}") + +set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") +set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") + +set (CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names") +set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names") +set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a") + +# hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree +# (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache +# and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun) +# hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex +if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool) +endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + +# Setup iOS platform unless specified manually with IOS_PLATFORM +if (NOT DEFINED IOS_PLATFORM) + set (IOS_PLATFORM "OS") +endif (NOT DEFINED IOS_PLATFORM) +set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform") + +# Check the platform selection and setup for developer root +if (${IOS_PLATFORM} STREQUAL "OS") + set (IOS_PLATFORM_LOCATION "iPhoneOS.platform") + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos") +elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR") + set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") +else (${IOS_PLATFORM} STREQUAL "OS") + message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR") +endif (${IOS_PLATFORM} STREQUAL "OS") + +# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT +# Note Xcode 4.3 changed the installation location, choose the most recent one available +execute_process( + COMMAND "xcode-select" "-p" + OUTPUT_VARIABLE "XCODE_PATH" + OUTPUT_STRIP_TRAILING_WHITESPACE ) +set (XCODE_POST_43_ROOT "${XCODE_PATH}/Platforms/${IOS_PLATFORM_LOCATION}/Developer") +set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer") +if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) + if (EXISTS ${XCODE_POST_43_ROOT}) + set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT}) + elseif(EXISTS ${XCODE_PRE_43_ROOT}) + set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT}) + endif (EXISTS ${XCODE_POST_43_ROOT}) +endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) +set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform") + +# Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT +if (NOT DEFINED CMAKE_IOS_SDK_ROOT) + file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*") + if (_CMAKE_IOS_SDKS) + list (SORT _CMAKE_IOS_SDKS) + list (REVERSE _CMAKE_IOS_SDKS) + list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT) + else (_CMAKE_IOS_SDKS) + message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.") + endif (_CMAKE_IOS_SDKS) + message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}") +endif (NOT DEFINED CMAKE_IOS_SDK_ROOT) +set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK") + +# Set the sysroot default to the most recent SDK +set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support") + +# set the architecture for iOS +# NOTE: Currently both ARCHS_STANDARD_32_BIT and ARCHS_UNIVERSAL_IPHONE_OS set armv7 only, so set both manually +if (${IOS_PLATFORM} STREQUAL "OS") + set (IOS_ARCH arm64) +else (${IOS_PLATFORM} STREQUAL "OS") + set (IOS_ARCH i386) +endif (${IOS_PLATFORM} STREQUAL "OS") + +set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE string "Build architecture for iOS") + +# Set the find root to the iOS developer roots and to user defined paths +set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE string "iOS find search path root") + +# default to searching for frameworks first +set (CMAKE_FIND_FRAMEWORK FIRST) + +# set up the default search directories for frameworks +set (CMAKE_SYSTEM_FRAMEWORK_PATH + ${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks + ${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks + ${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks +) + +# only search the iOS sdks, not the remainder of the host filesystem +set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +set( CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET "8.0" ) +set( CMAKE_XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" ) +set( CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE NO ) + +# This little macro lets you set any XCode specific property +macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE) + set_property (TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE}) +endmacro (set_xcode_property) + + +# This macro lets you find executable programs on the host system +macro (find_host_package) + set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) + set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) + set (IOS FALSE) + + find_package(${ARGN}) + + set (IOS TRUE) + set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) + set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endmacro (find_host_package) + diff --git a/extern/ios/Info.plist b/extern/ios/Info.plist new file mode 100644 index 00000000..00833fa8 --- /dev/null +++ b/extern/ios/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.example.gainput.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Launch Screen + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/extern/ios/Launch Screen.storyboard b/extern/ios/Launch Screen.storyboard new file mode 100644 index 00000000..73d30cc3 --- /dev/null +++ b/extern/ios/Launch Screen.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 00000000..5d53d1d3 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,79 @@ +project(gainput) +message(STATUS "GAINPUT version ${GAINPUT_VERSION}") + +set(CMAKE_MACOSX_RPATH 1) + +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++98 -Wall -Wextra -pedantic -Wshadow -Wno-variadic-macros") +endif() + +include_directories (include/) + +file(GLOB_RECURSE sources source/*.cpp source/*.h include/*.h) + +if(APPLE) + file(GLOB_RECURSE mmsources source/*.mm) +endif() + +## build STATIC *or* SHARED +if (GAINPUT_BUILD_SHARED) + message(STATUS "..Building shared libraries (-DGAINPUT_BUILD_SHARED=OFF to disable)") + add_library(gainput SHARED ${sources} ${mmsources}) + set_target_properties(gainput PROPERTIES + OUTPUT_NAME gainput + DEBUG_POSTFIX -d + VERSION ${GAINPUT_VERSION} + SOVERSION ${GAINPUT_MAJOR_VERSION} + FOLDER gainput + ) + set(install_libs ${install_libs} gainput) +endif (GAINPUT_BUILD_SHARED) + +if (GAINPUT_BUILD_STATIC) + message(STATUS "..Building static libraries (-DGAINPUT_BUILD_STATIC=OFF to disable)") + add_library(gainputstatic STATIC ${sources} ${mmsources}) + set_target_properties(gainputstatic PROPERTIES DEBUG_POSTFIX -d FOLDER gainput) + set(install_libs ${install_libs} gainputstatic) +endif (GAINPUT_BUILD_STATIC) + +if(WIN32) + target_link_libraries(gainput ${XINPUT} ws2_32) + target_link_libraries(gainputstatic ${XINPUT} ws2_32) + add_definitions(-DGAINPUT_LIB_DYNAMIC=1) +elseif(ANDROID) + target_link_libraries(gainputstatic native_app_glue log android) + target_link_libraries(gainput native_app_glue log android) +elseif(APPLE) + find_library(FOUNDATION Foundation) + find_library(IOKIT IOKit) + find_library(GAME_CONTROLLER GameController) + target_link_libraries(gainput ${FOUNDATION} ${IOKIT} ${GAME_CONTROLLER}) + if(IOS) + find_library(UIKIT UIKit) + find_library(COREMOTION CoreMotion) + find_library(QUARTZCORE QuartzCore) + target_link_libraries(gainput ${UIKIT} ${COREMOTION}) + else() + find_library(APPKIT AppKit) + target_link_libraries(gainput ${APPKIT}) + endif() +endif() + +# Library installation directory +if(NOT DEFINED CMAKE_INSTALL_LIBDIR) + set(CMAKE_INSTALL_LIBDIR lib) +endif(NOT DEFINED CMAKE_INSTALL_LIBDIR) +set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) + +install( + DIRECTORY "include/gainput" + DESTINATION "include" + FILES_MATCHING PATTERN "*.h" +) + +install( + TARGETS ${install_libs} + LIBRARY DESTINATION "${libdir}" + ARCHIVE DESTINATION "${libdir}" + RUNTIME DESTINATION "bin" +) diff --git a/lib/include/gainput/GainputAllocator.h b/lib/include/gainput/GainputAllocator.h new file mode 100644 index 00000000..54350ab1 --- /dev/null +++ b/lib/include/gainput/GainputAllocator.h @@ -0,0 +1,228 @@ + +#ifndef GAINPUTALLOCATOR_H_ +#define GAINPUTALLOCATOR_H_ + +namespace gainput +{ + +/// Interface used to pass custom allocators to the library. +/** + * If you want the library to use your custom allocator you should implement this interface. + * Specifically, you should provide implementations for the Allocate() and Deallocate() + * functions. All other (template) member functions are simply based on those two functions. + * + * \sa DefaultAllocator + */ +class GAINPUT_LIBEXPORT Allocator +{ +public: + enum { DefaultAlign = 0 }; + + /// Allocates a number of bytes and returns a pointer to the allocated memory. + /** + * \param size The number of bytes to allocate. + * \return A memory block encompassing at least size bytes. + */ + virtual void* Allocate(size_t size, size_t align = DefaultAlign) = 0; + /// Deallocates the given memory. + /** + * \param ptr The memory block to deallocate. + */ + virtual void Deallocate(void* ptr) = 0; + + /// An operator new-like function that allocates memory and calls T's constructor. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New() + { + return new (Allocate(sizeof(T))) T(); + } + + /// An operator new-like function that allocates memory and calls T's constructor with one parameter. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0) + { + return new (Allocate(sizeof(T))) T(p0); + } + + /// An operator new-like function that allocates memory and calls T's constructor with one parameter. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(const P0& p0) + { + return new (Allocate(sizeof(T))) T(p0); + } + + /// An operator new-like function that allocates memory and calls T's constructor with two parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, P1& p1) + { + return new (Allocate(sizeof(T))) T(p0, p1); + } + + /// An operator new-like function that allocates memory and calls T's constructor with two parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(const P0& p0, P1& p1) + { + return new (Allocate(sizeof(T))) T(p0, p1); + } + + /// An operator new-like function that allocates memory and calls T's constructor with two parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, const P1& p1) + { + return new (Allocate(sizeof(T))) T(p0, p1); + } + + /// An operator new-like function that allocates memory and calls T's constructor with the given parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, const P1& p1, const P2& p2) + { + return new (Allocate(sizeof(T))) T(p0, p1, p2); + } + + /// An operator new-like function that allocates memory and calls T's constructor with the given parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, const P1& p1, P2& p2) + { + return new (Allocate(sizeof(T))) T(p0, p1, p2); + } + + /// An operator new-like function that allocates memory and calls T's constructor with the given parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, P1& p1, P2& p2, P3& p3) + { + return new (Allocate(sizeof(T))) T(p0, p1, p2, p3); + } + + /// An operator new-like function that allocates memory and calls T's constructor with the given parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, const P1& p1, P2& p2, P3& p3) + { + return new (Allocate(sizeof(T))) T(p0, p1, p2, p3); + } + + /// An operator new-like function that allocates memory and calls T's constructor with the given parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, P1& p1, P2& p2, P3& p3, P4& p4) + { + return new (Allocate(sizeof(T))) T(p0, p1, p2, p3, p4); + } + + /// An operator new-like function that allocates memory and calls T's constructor with the given parameters. + /** + * \return A pointer to an initialized instance of T. + */ + template + T* New(P0& p0, const P1& p1, P2& p2, P3& p3, P4& p4) + { + return new (Allocate(sizeof(T))) T(p0, p1, p2, p3, p4); + } + + /// An operator delete-like function that calls ptr's constructor and deallocates the memory. + /** + * \param ptr The object to destruct and deallocate. + */ + template + void Delete(T* ptr) + { + if (ptr) + { + ptr->~T(); + Deallocate(ptr); + } + } +}; + +/// The default allocator used by the library. +/** + * Any allocation/deallocation calls are simply forwarded to \c malloc and \c free. Any + * requested alignment is ignored. + */ +class GAINPUT_LIBEXPORT DefaultAllocator : public Allocator +{ +public: + void* Allocate(size_t size, size_t /*align*/) + { + return malloc(size); + } + + void Deallocate(void* ptr) + { + free(ptr); + } +}; + +/// Returns the default instance of the default allocator. +/** + * \sa DefaultAllocator + */ +GAINPUT_LIBEXPORT DefaultAllocator& GetDefaultAllocator(); + +template +class GAINPUT_LIBEXPORT HashMap; + +/// An allocator that tracks an allocations that were done +/** + * Any allocation/deallocation calls are simply forwarded to \c malloc and \c free. Any + * requested alignment is ignored. + */ +class GAINPUT_LIBEXPORT TrackingAllocator : public Allocator +{ +public: + TrackingAllocator(Allocator& backingAllocator, Allocator& internalAllocator = GetDefaultAllocator()); + ~TrackingAllocator(); + + void* Allocate(size_t size, size_t align = DefaultAlign); + void Deallocate(void* ptr); + + size_t GetAllocateCount() const { return allocateCount_; } + size_t GetDeallocateCount() const { return deallocateCount_; } + size_t GetAllocatedMemory() const { return allocatedMemory_; } + +private: + Allocator& backingAllocator_; + Allocator& internalAllocator_; + HashMap* allocations_; + size_t allocateCount_; + size_t deallocateCount_; + size_t allocatedMemory_; +}; + + + +} + +#endif + diff --git a/lib/include/gainput/GainputContainers.h b/lib/include/gainput/GainputContainers.h new file mode 100644 index 00000000..c6fffe05 --- /dev/null +++ b/lib/include/gainput/GainputContainers.h @@ -0,0 +1,508 @@ + +#ifndef GAINPUTCONTAINERS_H_ +#define GAINPUTCONTAINERS_H_ + + +namespace gainput +{ + + +// -- MurmurHash3 begin -- +// http://code.google.com/p/smhasher/wiki/MurmurHash3 +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +inline uint32_t rotl32(uint32_t x, int8_t r) +{ + return (x << r) | (x >> (32 - r)); +} + +inline uint32_t getblock(const uint32_t * p, int i) +{ + return p[i]; +} + + +inline uint32_t fmix(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +/// Calculates MurmurHash3 for the given key. +/** + * \param key The key to calculate the hash of. + * \param len Length of the key in bytes. + * \param seed Seed for the hash. + * \param[out] out The hash value, a uint32_t in this case. + */ +inline void MurmurHash3_x86_32(const void * key, int len, uint32_t seed, void * out) +{ + const uint8_t * data = (const uint8_t*)key; + const int nblocks = len / 4; + + uint32_t h1 = seed; + + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + + const uint32_t * blocks = (const uint32_t *)(data + nblocks*4); + + for(int i = -nblocks; i; i++) + { + uint32_t k1 = getblock(blocks,i); + + k1 *= c1; + k1 = rotl32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = rotl32(h1,13); + h1 = h1*5+0xe6546b64; + } + + const uint8_t * tail = (const uint8_t*)(data + nblocks*4); + + uint32_t k1 = 0; + + switch(len & 3) + { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = rotl32(k1,15); k1 *= c2; h1 ^= k1; + }; + + h1 ^= len; + + h1 = fmix(h1); + + *(uint32_t*)out = h1; +} + +// -- MurmurHash3 end -- + + +/// A std::vector-like data container for POD-types. +/** + * \tparam T A POD-type to hold in this container. + */ +template +class GAINPUT_LIBEXPORT Array +{ +public: + static const size_t DefaultCapacity = 8; + + typedef T* iterator; + typedef const T* const_iterator; + typedef T value_type; + + Array(Allocator& allocator, size_t capacity = DefaultCapacity) : + allocator_(allocator), + size_(0), + capacity_(capacity) + { + data_ = static_cast(allocator_.Allocate(sizeof(T)*capacity_)); + } + + ~Array() + { + allocator_.Delete(data_); + } + + iterator begin() { return data_; } + const_iterator begin() const { return data_; } + iterator end() { return data_ + size_; } + const_iterator end() const { return data_ + size_; } + + T& operator[] (size_t i) + { + GAINPUT_ASSERT(i < size_); + return data_[i]; + } + + const T& operator[] (size_t i) const + { + GAINPUT_ASSERT(i < size_); + return data_[i]; + } + + void push_back(const value_type& val) + { + if (size_ + 1 > capacity_) + { + reserve(size_ + 1); + } + data_[size_++] = val; + } + + void pop_back() + { + if (size_ > 0) + --size_; + } + + void reserve(size_t capacity) + { + if (capacity <= capacity_) + return; + capacity = (capacity_*2) < capacity ? capacity : (capacity_*2); + T* newData = static_cast(allocator_.Allocate(sizeof(T)*capacity)); + memcpy(newData, data_, sizeof(T)*capacity_); + allocator_.Deallocate(data_); + data_ = newData; + capacity_ = capacity; + } + + void swap(Array& x) + { + GAINPUT_ASSERT(&allocator_ == &x.allocator_); + + const size_t thisSize = size_; + const size_t capacity = capacity_; + T* data = data_; + + size_ = x.size_; + capacity_ = x.capacity_; + data_ = x.data_; + + x.size_ = thisSize; + x.capacity_ = capacity; + x.data_ = data; + } + + iterator erase(iterator pos) + { + if (size_ == 0) + return end(); + GAINPUT_ASSERT(pos >= begin() && pos < end()); + memcpy(pos, pos+1, sizeof(T)*(end()-(pos+1))); + --size_; + return pos; + } + + void clear() { size_ = 0; } + + bool empty() const { return size_ == 0; } + size_t size() const { return size_; } + + iterator find(const value_type& val) + { + for (size_t i = 0; i < size_; ++i) + { + if (data_[i] == val) + { + return data_ + i; + } + } + return end(); + } + + const_iterator find(const value_type& val) const + { + for (size_t i = 0; i < size_; ++i) + { + if (data_[i] == val) + { + return data_ + i; + } + } + return end(); + } + +private: + Allocator& allocator_; + size_t size_; + size_t capacity_; + T* data_; +}; + + +/// A hash table mapping keys to POD-type values. +/** + * \tparam K The key pointing to a value. + * \tparam V POD-type being stored in the table. + */ +template +class GAINPUT_LIBEXPORT HashMap +{ +public: + static const unsigned Seed = 329856235; + enum { InvalidKey = unsigned(-1) }; + + /// An element of the hash table. + struct Node + { + K first; ///< The element's key. + V second; ///< The element's value. + uint32_t next; ///< The index of the next element with the same (wrapped) hash; Do not use. + }; + + typedef Node* iterator; + typedef const Node* const_iterator; + + + HashMap(Allocator& allocator = GetDefaultAllocator()) : + allocator_(allocator), + keys_(allocator_), + values_(allocator_), + size_(0) + { } + + iterator begin() { return values_.begin(); } + const_iterator begin() const { return values_.begin(); } + iterator end() { return values_.begin() + values_.size(); } + const_iterator end() const { return values_.begin() + values_.size(); } + + size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + + size_t count(const K& k) const + { + return find(k) != end() ? 1 : 0; + } + + iterator find(const K& k) + { + if (keys_.empty() || values_.empty()) + return end(); + uint32_t h; + MurmurHash3_x86_32(&k, sizeof(K), Seed, &h); + const uint32_t ha = h % keys_.size(); + volatile uint32_t vi = keys_[ha]; + while (vi != InvalidKey) + { + if (values_[vi].first == k) + { + return &values_[vi]; + } + vi = values_[vi].next; + } + return end(); + } + + const_iterator find(const K& k) const + { + if (keys_.empty() || values_.empty()) + return end(); + uint32_t h; + MurmurHash3_x86_32(&k, sizeof(K), Seed, &h); + const uint32_t ha = h % keys_.size(); + volatile uint32_t vi = keys_[ha]; + while (vi != InvalidKey) + { + if (values_[vi].first == k) + { + return &values_[vi]; + } + vi = values_[vi].next; + } + return end(); + } + + iterator insert(const K& k, const V& v) + { + if (values_.size() >= 0.6f*keys_.size()) + { + Rehash(values_.size()*2 + 10); + } + + uint32_t h; + MurmurHash3_x86_32(&k, sizeof(K), Seed, &h); + const uint32_t ha = h % keys_.size(); + uint32_t vi = keys_[ha]; + + if (vi == InvalidKey) + { + keys_[ha] = values_.size(); + } + else + { + for (;;) + { + if (values_[vi].next == InvalidKey) + { + values_[vi].next = values_.size(); + break; + } + else + { + vi = values_[vi].next; + } + } + } + + Node node; + node.first = k; + node.second = v; + node.next = InvalidKey; + values_.push_back(node); + + ++size_; + + return &values_[values_.size()-1]; + } + + V& operator[] (const K& k) + { + iterator it = find(k); + if (it == end()) + { + return insert(k, V())->second; + } + else + { + return it->second; + } + } + + size_t erase(const K& k) + { + if (keys_.empty()) + return 0; + uint32_t h; + MurmurHash3_x86_32(&k, sizeof(K), Seed, &h); + const uint32_t ha = h % keys_.size(); + uint32_t vi = keys_[ha]; + uint32_t prevVi = InvalidKey; + while (vi != InvalidKey) + { + if (values_[vi].first == k) + { + if (prevVi == InvalidKey) + { + keys_[ha] = values_[vi].next; + } + else + { + values_[prevVi].next = values_[vi].next; + } + + --size_; + if (vi == values_.size() - 1) + { + values_.pop_back(); + return 1; + } + else + { + size_t lastVi = values_.size()-1; + values_[vi] = values_[lastVi]; + values_.pop_back(); + + for (typename Array::iterator it = keys_.begin(); it != keys_.end(); ++it) + { + if (*it == lastVi) + { + *it = vi; + break; + } + } + for (typename Array::iterator it = values_.begin(); it != values_.end(); ++it) + { + if (it->next == lastVi) + { + it->next = vi; + break; + } + } + return 1; + } + } + prevVi = vi; + vi = values_[vi].next; + } + return 0; + } + + void clear() + { + keys_.clear(); + values_.clear(); + } + +private: + Allocator& allocator_; + Array keys_; + Array values_; + size_t size_; + + void Rehash(size_t newSize) + { + Array keys(allocator_, newSize); + Array values(allocator_, values_.size()); + + for (size_t i = 0; i < newSize; ++i) + keys.push_back(InvalidKey); + + keys_.swap(keys); + values_.swap(values); + size_ = 0; + + for (typename Array::const_iterator it = values.begin(); + it != values.end(); + ++it) + { + insert(it->first, it->second); + } + } + +}; + + + +/// A ring buffer. +/** + * \tparam N The number of elements that can be stored in the ring buffer. + * \tparam T Type of the elements stored in the ring buffer. + */ +template +class GAINPUT_LIBEXPORT RingBuffer +{ +public: + RingBuffer() : + nextRead_(0), + nextWrite_(0) + { } + + + bool CanGet() const + { + return nextRead_ < nextWrite_; + } + + size_t GetCount() const + { + const size_t d = nextWrite_ - nextRead_; + return d > N ? N : d; + } + + T Get() + { + return buf_[(nextRead_++) % N]; + } + + void Put(T d) + { + buf_[(nextWrite_++) % N] = d; + while (nextRead_ + N < nextWrite_) + ++nextRead_; + } + +private: + T buf_[N]; + size_t nextRead_; + size_t nextWrite_; +}; + + + +} + +#endif + diff --git a/lib/include/gainput/GainputDebugRenderer.h b/lib/include/gainput/GainputDebugRenderer.h new file mode 100644 index 00000000..7c6b5cb2 --- /dev/null +++ b/lib/include/gainput/GainputDebugRenderer.h @@ -0,0 +1,34 @@ + +#ifndef GAINPUTDEBUGRENDERER_H_ +#define GAINPUTDEBUGRENDERER_H_ + +namespace gainput +{ + +/// Interface for debug rendering of input device states. +/** + * Coordinates and other measures passed to the interface's functions are in the + * range of 0.0f to 1.0f. + * + * The functions are called as part InputManager::Update(). + */ +class GAINPUT_LIBEXPORT DebugRenderer +{ +public: + /// Empty virtual destructor. + virtual ~DebugRenderer() { } + + /// Called to draw a circle with the given radius. + virtual void DrawCircle(float x, float y, float radius) = 0; + + /// Called to draw a line between the two given points. + virtual void DrawLine(float x1, float y1, float x2, float y2) = 0; + + /// Called to draw some text at the given position. + virtual void DrawText(float x, float y, const char* const text) = 0; +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputHelpers.h b/lib/include/gainput/GainputHelpers.h new file mode 100644 index 00000000..ddfae417 --- /dev/null +++ b/lib/include/gainput/GainputHelpers.h @@ -0,0 +1,71 @@ + +#ifndef GAINPUTHELPERS_H_ +#define GAINPUTHELPERS_H_ + +#include + +namespace gainput +{ + + inline void HandleButton(InputDevice& device, InputState& state, InputDeltaState* delta, DeviceButtonId buttonId, bool value) + { +#ifdef GAINPUT_DEBUG + if (value != state.GetBool(buttonId)) + { + GAINPUT_LOG("Button changed: %d, %i\n", buttonId, value); + } +#endif + + if (delta) + { + const bool oldValue = state.GetBool(buttonId); + if (value != oldValue) + { + delta->AddChange(device.GetDeviceId(), buttonId, oldValue, value); + } + } + + state.Set(buttonId, value); + } + + inline void HandleAxis(InputDevice& device, InputState& state, InputDeltaState* delta, DeviceButtonId buttonId, float value) + { + const float deadZone = device.GetDeadZone(buttonId); + if (deadZone > 0.0f) + { + const float absValue = Abs(value); + const float sign = value < 0.0f ? -1.0f : 1.0f; + if (absValue < deadZone) + { + value = 0.0f; + } + else + { + value -= sign*deadZone; + value *= 1.0f / (1.0f - deadZone); + } + } +#ifdef GAINPUT_DEBUG + if (value != state.GetFloat(buttonId)) + { + GAINPUT_LOG("Axis changed: %d, %f\n", buttonId, value); + } +#endif + + if (delta) + { + const float oldValue = state.GetFloat(buttonId); + if (value != oldValue) + { + delta->AddChange(device.GetDeviceId(), buttonId, oldValue, value); + } + } + + state.Set(buttonId, value); + } + +} + +#endif + + diff --git a/lib/include/gainput/GainputInputDeltaState.h b/lib/include/gainput/GainputInputDeltaState.h new file mode 100644 index 00000000..a8da73b0 --- /dev/null +++ b/lib/include/gainput/GainputInputDeltaState.h @@ -0,0 +1,59 @@ + +#ifndef GAINPUTINPUTDELTASTATE_H_ +#define GAINPUTINPUTDELTASTATE_H_ + +namespace gainput +{ + +/// Stores a list of input state changes. +class GAINPUT_LIBEXPORT InputDeltaState +{ +public: + InputDeltaState(Allocator& allocator); + + /// Add a state change for a bool-type button. + /** + * \param device The input device the change occurred on. + * \param deviceButton The input button that was changed. + * \param oldValue The old button state. + * \param newValue The new button state. + */ + void AddChange(DeviceId device, DeviceButtonId deviceButton, bool oldValue, bool newValue); + /// Add a state change for a float-type button. + /** + * \param device The input device the change occurred on. + * \param deviceButton The input button that was changed. + * \param oldValue The old button state. + * \param newValue The new button state. + */ + void AddChange(DeviceId device, DeviceButtonId deviceButton, float oldValue, float newValue); + + /// Clear list of state changes. + void Clear(); + + /// Notifies all input listeners of the previously recorded state changes. + /** + * \param listeners A list of input listeners to notify. + */ + void NotifyListeners(Array& listeners) const; + +private: + struct Change + { + DeviceId device; + DeviceButtonId deviceButton; + ButtonType type; + union + { + bool b; + float f; + } oldValue, newValue; + }; + + Array changes_; +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputDevice.h b/lib/include/gainput/GainputInputDevice.h new file mode 100644 index 00000000..d1c97166 --- /dev/null +++ b/lib/include/gainput/GainputInputDevice.h @@ -0,0 +1,271 @@ + +#ifndef GAINPUTINPUTDEVICE_H_ +#define GAINPUTINPUTDEVICE_H_ + +namespace gainput +{ + + +/// Type of an input device button. +enum ButtonType +{ + BT_BOOL, ///< A boolean value button, either down (true) or up (false). + BT_FLOAT, ///< A floating-point value button, between -1.0f and 1.0f or 0.0f and 1.0f. + BT_COUNT ///< The number of different button types. +}; + + +/// Interface for anything that provides device inputs. +/** + * An InputDevice can be anything from a physical device, like a mouse or keyboard, to the more abstract + * concept of gestures that in turn themselves depend on other InputDevices. What they have in common is + * that they provide a number of device buttons (identified by a DeviceButtonId) which can be queried for + * their state. + * + * Note that you may not instantiate an InputDevice (or any derived implementation) directly. Instead you + * have to call InputManager::CreateDevice() or InputManager::CreateAndGetDevice() with the device you want + * to instantiate as the template parameter. That way the device will be properly registered with the + * InputManager and continuously updated. + * + * Normally, you won't interact with an InputDevice directly, but instead use its device ID and its device + * buttons' IDs to map the device buttons to user buttons (see InputMap). + */ +class GAINPUT_LIBEXPORT InputDevice +{ +public: + /// Type of an input device. + enum DeviceType + { + DT_MOUSE, ///< A mouse/cursor input device featuring one pointer. + DT_KEYBOARD, ///< A keyboard input device. + DT_PAD, ///< A joypad/gamepad input device. + DT_TOUCH, ///< A touch-sensitive input device supporting multiple simultaneous pointers. + DT_BUILTIN, ///< Any controls directly built into the device that also contains the screen. + DT_REMOTE, ///< A generic networked input device. + DT_GESTURE, ///< A gesture input device, building on top of other input devices. + DT_CUSTOM, ///< A custom, user-created input device. + DT_COUNT ///< The count of input device types. + }; + + /// Variant of an input device type. + enum DeviceVariant + { + DV_STANDARD, ///< The standard implementation of the given device type. + DV_RAW, ///< The raw input implementation of the given device type. + DV_NULL ///< The null/empty implementation of the given device type. + }; + + /// State of an input device. + enum DeviceState + { + DS_OK, ///< Everything is okay. + DS_LOW_BATTERY, ///< The input device is low on battery. + DS_UNAVAILABLE ///< The input device is currently not available. + }; + + static const unsigned AutoIndex = unsigned(-1); + + /// Initializes the input device. + /** + * Do not instantiate any input device directly. Call InputManager::CreateDevice() instead. + */ + InputDevice(InputManager& manager, DeviceId device, unsigned index); + + /// Empty virtual destructor. + virtual ~InputDevice(); + + /// Update this device, internally called by InputManager. + /** + * \param delta The delta state to add changes to. May be 0. + */ + void Update(InputDeltaState* delta); + + /// Returns this device's ID. + DeviceId GetDeviceId() const { return deviceId_; } + /// Returns the device's index among devices of the same type. + unsigned GetIndex() const { return index_; } + + /// Returns the device type. + virtual DeviceType GetType() const = 0; + /// Returns the device variant. + virtual DeviceVariant GetVariant() const { return DV_STANDARD; } + /// Returns the device type's name. + virtual const char* GetTypeName() const = 0; + /// Returns if this device should be updated after other devices. + virtual bool IsLateUpdate() const { return false; } + /// Returns the device state. + DeviceState GetState() const; + /// Returns if this device is available. + virtual bool IsAvailable() const { return GetState() == DS_OK || GetState() == DS_LOW_BATTERY; } + + /// Returns if the given button is valid for this device. + virtual bool IsValidButtonId(DeviceButtonId deviceButton) const = 0; + + /// Returns the current state of the given button. + bool GetBool(DeviceButtonId deviceButton) const; + /// Returns the previous state of the given button. + bool GetBoolPrevious(DeviceButtonId deviceButton) const; + /// Returns the current state of the given button. + float GetFloat(DeviceButtonId deviceButton) const; + /// Returns the previous state of the given button. + float GetFloatPrevious(DeviceButtonId deviceButton) const; + + /// Checks if any button on this device is down. + /** + * \param[out] outButtons An array with maxButtonCount fields to receive the device buttons that are down. + * \param maxButtonCount The number of fields in outButtons. + * \return The number of device buttons written to outButtons. + */ + virtual size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const { GAINPUT_UNUSED(outButtons); GAINPUT_UNUSED(maxButtonCount); return 0; } + + /// Gets the name of the given button. + /** + * \param deviceButton ID of the button. + * \param buffer A char-buffer to receive the button name. + * \param bufferLength Length of the buffer receiving the button name in bytes. + * \return The number of bytes written to buffer (includes the trailing \0). + */ + virtual size_t GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const { GAINPUT_UNUSED(deviceButton); GAINPUT_UNUSED(buffer); GAINPUT_UNUSED(bufferLength); return 0; } + /// Returns the type of the given button. + virtual ButtonType GetButtonType(DeviceButtonId deviceButton) const = 0; + + /// Returns the button's ID if the name is of this device's buttons. + /** + * \param name Name of the device button to look for. + * \return The device button ID. + */ + virtual DeviceButtonId GetButtonByName(const char* name) const { GAINPUT_UNUSED(name); return InvalidDeviceButtonId; } + + /// Returns the device's state, probably best if only used internally. + InputState* GetInputState() { return state_; } + /// Returns the device's state, probably best if only used internally. + const InputState* GetInputState() const { return state_; } + /// Returns the device's previous state, probably best if only used internally. + InputState* GetPreviousInputState() { return previousState_; } + /// Returns the device's state that is currently being determined, may be 0 if not available. + virtual InputState* GetNextInputState() { return 0; } + + /// Returns the previously set dead zone for the given button or 0.0f if none was set yet. + float GetDeadZone(DeviceButtonId buttonId) const; + /// Sets the dead zone for the given button. + void SetDeadZone(DeviceButtonId buttonId, float value); + + /// Enable/disable debug rendering of this device. + void SetDebugRenderingEnabled(bool enabled); + /// Returns true if debug rendering is enabled, false otherwise. + bool IsDebugRenderingEnabled() const { return debugRenderingEnabled_; } + +#if defined(GAINPUT_DEV) || defined(GAINPUT_ENABLE_RECORDER) + /// Returns true if this device is being controlled by a remote device + /// or a recorded input sequence, false otherwise. + bool IsSynced() const { return synced_; } + /// Sets if this device is being controlled remotely or from a recording. + /** + * \sa IsSynced() + */ + void SetSynced(bool synced) { synced_ = synced; } +#endif + +protected: + /// The manager this device belongs to. + InputManager& manager_; + + /// The ID of this device. + DeviceId deviceId_; + + /// Index of this device among devices of the same type. + unsigned index_; + + /// The current state of this device. + InputState* state_; + /// The previous state of this device. + InputState* previousState_; + + float* deadZones_; + + /// Specifies if this device is currently rendering debug information. + bool debugRenderingEnabled_; + +#if defined(GAINPUT_DEV) || defined(GAINPUT_ENABLE_RECORDER) + /// Specifies if this device's state is actually set from a device + /// or manually set by some other system. + bool synced_; +#endif + + /// Implementation of the device's Update function. + /** + * \param delta The delta state to add changes to. May be 0. + */ + virtual void InternalUpdate(InputDeltaState* delta) = 0; + + /// Implementation of the device's GetState function. + /** + * \return The device's state. + */ + virtual DeviceState InternalGetState() const = 0; + + /// Checks which buttons are down. + /** + * This function is normally used by GetAnyButtonDown implementations internally. + * \param outButtons An array to write buttons that are down to. + * \param maxButtonCount The size of outButtons. + * \param start The lowest device button ID to check. + * \param end The biggest device button ID to check. + * \return The number of buttons written to outButtons. + */ + size_t CheckAllButtonsDown(DeviceButtonSpec* outButtons, size_t maxButtonCount, unsigned start, unsigned end) const; +}; + + +inline +bool +InputDevice::GetBool(DeviceButtonId deviceButton) const +{ + if (!IsAvailable()) + { + return false; + } + GAINPUT_ASSERT(state_); + return state_->GetBool(deviceButton); +} + +inline +bool +InputDevice::GetBoolPrevious(DeviceButtonId deviceButton) const +{ + if (!IsAvailable()) + { + return false; + } + GAINPUT_ASSERT(previousState_); + return previousState_->GetBool(deviceButton); +} + +inline +float +InputDevice::GetFloat(DeviceButtonId deviceButton) const +{ + if (!IsAvailable()) + { + return 0.0f; + } + GAINPUT_ASSERT(state_); + return state_->GetFloat(deviceButton); +} + +inline +float +InputDevice::GetFloatPrevious(DeviceButtonId deviceButton) const +{ + if (!IsAvailable()) + { + return 0.0f; + } + GAINPUT_ASSERT(previousState_); + return previousState_->GetFloat(deviceButton); +} + +} + +#endif + diff --git a/lib/include/gainput/GainputInputDeviceBuiltIn.h b/lib/include/gainput/GainputInputDeviceBuiltIn.h new file mode 100644 index 00000000..5cb2d0e8 --- /dev/null +++ b/lib/include/gainput/GainputInputDeviceBuiltIn.h @@ -0,0 +1,68 @@ + +#ifndef GAINPUTINPUTDEVICEBUILTIN_H_ +#define GAINPUTINPUTDEVICEBUILTIN_H_ + +namespace gainput +{ + +/// All valid device buttons for InputDeviceBuiltIn. +enum BuiltInButton +{ + BuiltInButtonAccelerationX, + BuiltInButtonAccelerationY, + BuiltInButtonAccelerationZ, + BuiltInButtonGravityX, + BuiltInButtonGravityY, + BuiltInButtonGravityZ, + BuiltInButtonGyroscopeX, + BuiltInButtonGyroscopeY, + BuiltInButtonGyroscopeZ, + BuiltInButtonMagneticFieldX, + BuiltInButtonMagneticFieldY, + BuiltInButtonMagneticFieldZ, + BuiltInButtonCount_ +}; + +class InputDeviceBuiltInImpl; + +/// An input device for inputs that are directly built into the executing device (for example, sensors in a phone). +class GAINPUT_LIBEXPORT InputDeviceBuiltIn : public InputDevice +{ +public: + /// Initializes the device. + /** + * Instantiate the device using InputManager::CreateDevice(). + * + * \param manager The input manager this device is managed by. + * \param device The ID of this device. + */ + InputDeviceBuiltIn(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Shuts down the device. + ~InputDeviceBuiltIn(); + + /// Returns DT_BUILTIN. + DeviceType GetType() const { return DT_BUILTIN; } + DeviceVariant GetVariant() const; + const char* GetTypeName() const { return "builtin"; } + bool IsValidButtonId(DeviceButtonId deviceButton) const; + + size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + size_t GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const; + ButtonType GetButtonType(DeviceButtonId deviceButton) const; + DeviceButtonId GetButtonByName(const char* name) const; + +protected: + void InternalUpdate(InputDeltaState* delta); + + DeviceState InternalGetState() const; + +private: + InputDeviceBuiltInImpl* impl_; + +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputDeviceKeyboard.h b/lib/include/gainput/GainputInputDeviceKeyboard.h new file mode 100644 index 00000000..16f41668 --- /dev/null +++ b/lib/include/gainput/GainputInputDeviceKeyboard.h @@ -0,0 +1,267 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARD_H_ +#define GAINPUTINPUTDEVICEKEYBOARD_H_ + +namespace gainput +{ + +/// All valid device buttons for InputDeviceKeyboard. +enum Key +{ + KeyEscape, + KeyF1, + KeyF2, + KeyF3, + KeyF4, + KeyF5, + KeyF6, + KeyF7, + KeyF8, + KeyF9, + KeyF10, + KeyF11, + KeyF12, + KeyF13, + KeyF14, + KeyF15, + KeyF16, + KeyF17, + KeyF18, + KeyF19, + KeyPrint, + KeyScrollLock, + KeyBreak, + + KeySpace = 0x0020, + + KeyApostrophe = 0x0027, + KeyComma = 0x002c, + KeyMinus = 0x002d, + KeyPeriod = 0x002e, + KeySlash = 0x002f, + + Key0 = 0x0030, + Key1 = 0x0031, + Key2 = 0x0032, + Key3 = 0x0033, + Key4 = 0x0034, + Key5 = 0x0035, + Key6 = 0x0036, + Key7 = 0x0037, + Key8 = 0x0038, + Key9 = 0x0039, + + KeySemicolon = 0x003b, + KeyLess = 0x003c, + KeyEqual = 0x003d, + + KeyA = 0x0041, + KeyB = 0x0042, + KeyC = 0x0043, + KeyD = 0x0044, + KeyE = 0x0045, + KeyF = 0x0046, + KeyG = 0x0047, + KeyH = 0x0048, + KeyI = 0x0049, + KeyJ = 0x004a, + KeyK = 0x004b, + KeyL = 0x004c, + KeyM = 0x004d, + KeyN = 0x004e, + KeyO = 0x004f, + KeyP = 0x0050, + KeyQ = 0x0051, + KeyR = 0x0052, + KeyS = 0x0053, + KeyT = 0x0054, + KeyU = 0x0055, + KeyV = 0x0056, + KeyW = 0x0057, + KeyX = 0x0058, + KeyY = 0x0059, + KeyZ = 0x005a, + + KeyBracketLeft = 0x005b, + KeyBackslash = 0x005c, + KeyBracketRight = 0x005d, + + KeyGrave = 0x0060, + + KeyLeft, + KeyRight, + KeyUp, + KeyDown, + KeyInsert, + KeyHome, + KeyDelete, + KeyEnd, + KeyPageUp, + KeyPageDown, + + KeyNumLock, + KeyKpEqual, + KeyKpDivide, + KeyKpMultiply, + KeyKpSubtract, + KeyKpAdd, + KeyKpEnter, + KeyKpInsert, // 0 + KeyKpEnd, // 1 + KeyKpDown, // 2 + KeyKpPageDown, // 3 + KeyKpLeft, // 4 + KeyKpBegin, // 5 + KeyKpRight, // 6 + KeyKpHome, // 7 + KeyKpUp, // 8 + KeyKpPageUp, // 9 + KeyKpDelete, // , + + KeyBackSpace, + KeyTab, + KeyReturn, + KeyCapsLock, + KeyShiftL, + KeyCtrlL, + KeySuperL, + KeyAltL, + KeyAltR, + KeySuperR, + KeyMenu, + KeyCtrlR, + KeyShiftR, + + KeyBack, + KeySoftLeft, + KeySoftRight, + KeyCall, + KeyEndcall, + KeyStar, + KeyPound, + KeyDpadCenter, + KeyVolumeUp, + KeyVolumeDown, + KeyPower, + KeyCamera, + KeyClear, + KeySymbol, + KeyExplorer, + KeyEnvelope, + KeyEquals, + KeyAt, + KeyHeadsethook, + KeyFocus, + KeyPlus, + KeyNotification, + KeySearch, + KeyMediaPlayPause, + KeyMediaStop, + KeyMediaNext, + KeyMediaPrevious, + KeyMediaRewind, + KeyMediaFastForward, + KeyMute, + KeyPictsymbols, + KeySwitchCharset, + + KeyForward, + KeyExtra1, + KeyExtra2, + KeyExtra3, + KeyExtra4, + KeyExtra5, + KeyExtra6, + KeyFn, + + KeyCircumflex, + KeySsharp, + KeyAcute, + KeyAltGr, + KeyNumbersign, + KeyUdiaeresis, + KeyAdiaeresis, + KeyOdiaeresis, + KeySection, + KeyAring, + KeyDiaeresis, + KeyTwosuperior, + KeyRightParenthesis, + KeyDollar, + KeyUgrave, + KeyAsterisk, + KeyColon, + KeyExclam, + + KeyBraceLeft, + KeyBraceRight, + KeySysRq, + + KeyCount_ +}; + + +class InputDeviceKeyboardImpl; + +/// A keyboard input device. +/** + * This input device provides support for standard keyboard devices. The valid device buttons are defined + * in the ::Key enum. + * + * This device is implemented on Android NDK, Linux, and Windows. Note that no support for + * virtual keyboards (on-screen) is present. + * + * The raw variants (InputDevice::DV_RAW) of this device do not support text input. + */ +class GAINPUT_LIBEXPORT InputDeviceKeyboard : public InputDevice +{ +public: + /// Initializes the device. + /** + * Instantiate the device using InputManager::CreateDevice(). + * + * \param manager The input manager this device is managed by. + * \param device The ID of this device. + */ + InputDeviceKeyboard(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Shuts down the device. + ~InputDeviceKeyboard(); + + /// Returns DT_KEYBOARD. + DeviceType GetType() const { return DT_KEYBOARD; } + DeviceVariant GetVariant() const; + const char* GetTypeName() const { return "keyboard"; } + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton < KeyCount_; } + + size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + size_t GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const; + ButtonType GetButtonType(DeviceButtonId deviceButton) const; + DeviceButtonId GetButtonByName(const char* name) const; + + InputState* GetNextInputState(); + + /// Returns if text input is enabled. + bool IsTextInputEnabled() const; + /// Sets if text input is enabled and therefore if calling GetNextCharacter() make sense. + void SetTextInputEnabled(bool enabled); + /// Returns the next pending input character if text input is enabled. + char GetNextCharacter(); + + /// Returns the platform-specific implementation of this device (internal use only). + InputDeviceKeyboardImpl* GetPimpl() { return impl_; } + +protected: + void InternalUpdate(InputDeltaState* delta); + DeviceState InternalGetState() const; + +private: + InputDeviceKeyboardImpl* impl_; + + HashMap keyNames_; +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputDeviceMouse.h b/lib/include/gainput/GainputInputDeviceMouse.h new file mode 100644 index 00000000..5b19dfb1 --- /dev/null +++ b/lib/include/gainput/GainputInputDeviceMouse.h @@ -0,0 +1,101 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSE_H_ +#define GAINPUTINPUTDEVICEMOUSE_H_ + +namespace gainput +{ + +/// All valid device buttons for InputDeviceMouse. +enum MouseButton +{ + MouseButton0 = 0, + MouseButtonLeft = MouseButton0, + MouseButton1, + MouseButtonMiddle = MouseButton1, + MouseButton2, + MouseButtonRight = MouseButton2, + MouseButton3, + MouseButtonWheelUp = MouseButton3, + MouseButton4, + MouseButtonWheelDown = MouseButton4, + MouseButton5, + MouseButton6, + MouseButton7, + MouseButton8, + MouseButton9, + MouseButton10, + MouseButton11, + MouseButton12, + MouseButton13, + MouseButton14, + MouseButton15, + MouseButton16, + MouseButton17, + MouseButton18, + MouseButton19, + MouseButton20, + MouseButtonMax = MouseButton20, + MouseButtonCount, + MouseAxisX = MouseButtonCount, + MouseAxisY, + MouseButtonCount_, + MouseAxisCount = MouseButtonCount_ - MouseAxisX +}; + + + +class InputDeviceMouseImpl; + +/// A mouse input device. +/** + * This input device provides support for standard mouse devices. The valid device buttons are defined + * in the ::MouseButton enum. + * + * This device is implemented on Linux and Windows. + * + * The raw variants (InputDevice::DV_RAW) of this device do not offer normalized absolute axis values. + * That means that the values of MouseAxisX and MouseAxisY don't have defined mininum or maximum + * values. Therefore only the delta (InputMap::GetFloatDelta()) should be used with raw mouse devices. + */ +class GAINPUT_LIBEXPORT InputDeviceMouse : public InputDevice +{ +public: + /// Initializes the device. + /** + * Instantiate the device using InputManager::CreateDevice(). + * + * \param manager The input manager this device is managed by. + * \param device The ID of this device. + */ + InputDeviceMouse(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Shuts down the device. + ~InputDeviceMouse(); + + /// Returns DT_MOUSE. + DeviceType GetType() const { return DT_MOUSE; } + DeviceVariant GetVariant() const; + const char* GetTypeName() const { return "mouse"; } + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton < MouseButtonCount_; } + + size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + size_t GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const; + ButtonType GetButtonType(DeviceButtonId deviceButton) const; + DeviceButtonId GetButtonByName(const char* name) const; + + /// Returns the platform-specific implementation of this device (internal use only). + InputDeviceMouseImpl* GetPimpl() { return impl_; } + +protected: + void InternalUpdate(InputDeltaState* delta); + DeviceState InternalGetState() const; + +private: + InputDeviceMouseImpl* impl_; + +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputDevicePad.h b/lib/include/gainput/GainputInputDevicePad.h new file mode 100644 index 00000000..366fcd83 --- /dev/null +++ b/lib/include/gainput/GainputInputDevicePad.h @@ -0,0 +1,163 @@ + +#ifndef GAINPUTINPUTDEVICEPAD_H_ +#define GAINPUTINPUTDEVICEPAD_H_ + +namespace gainput +{ + +/// The maximum number of pads supported. +enum { MaxPadCount = 10 }; + +/// All valid device buttons for InputDevicePad. +enum PadButton +{ + PadButtonLeftStickX, + PadButtonLeftStickY, + PadButtonRightStickX, + PadButtonRightStickY, + PadButtonAxis4, // L2/Left trigger + PadButtonAxis5, // R2/Right trigger + PadButtonAxis6, + PadButtonAxis7, + PadButtonAxis8, + PadButtonAxis9, + PadButtonAxis10, + PadButtonAxis11, + PadButtonAxis12, + PadButtonAxis13, + PadButtonAxis14, + PadButtonAxis15, + PadButtonAxis16, + PadButtonAxis17, + PadButtonAxis18, + PadButtonAxis19, + PadButtonAxis20, + PadButtonAxis21, + PadButtonAxis22, + PadButtonAxis23, + PadButtonAxis24, + PadButtonAxis25, + PadButtonAxis26, + PadButtonAxis27, + PadButtonAxis28, + PadButtonAxis29, + PadButtonAxis30, + PadButtonAxis31, + PadButtonAccelerationX, + PadButtonAccelerationY, + PadButtonAccelerationZ, + PadButtonGravityX, + PadButtonGravityY, + PadButtonGravityZ, + PadButtonGyroscopeX, + PadButtonGyroscopeY, + PadButtonGyroscopeZ, + PadButtonMagneticFieldX, + PadButtonMagneticFieldY, + PadButtonMagneticFieldZ, + PadButtonStart, + PadButtonAxisCount_ = PadButtonStart, + PadButtonSelect, + PadButtonLeft, + PadButtonRight, + PadButtonUp, + PadButtonDown, + PadButtonA, // Cross + PadButtonB, // Circle + PadButtonX, // Square + PadButtonY, // Triangle + PadButtonL1, + PadButtonR1, + PadButtonL2, + PadButtonR2, + PadButtonL3, // Left thumb + PadButtonR3, // Right thumb + PadButtonHome, // PS button + PadButton17, + PadButton18, + PadButton19, + PadButton20, + PadButton21, + PadButton22, + PadButton23, + PadButton24, + PadButton25, + PadButton26, + PadButton27, + PadButton28, + PadButton29, + PadButton30, + PadButton31, + PadButtonMax_, + PadButtonCount_ = PadButtonMax_ - PadButtonAxisCount_ +}; + +class InputDevicePadImpl; + +/// A pad input device. +/** + * This input device provides support for gamepad devices. The valid device buttons are defined + * in the ::PadButton enum. + * + * This device is implemented on Android NDK, Linux and Windows. + * + * Note that the Android implementation does not support any external pads, but only internal + * sensors (acceleration, gyroscope, magnetic field). + */ +class GAINPUT_LIBEXPORT InputDevicePad : public InputDevice +{ +public: + /// The operating system device IDs for all possible pads. + static const char* PadDeviceIds[MaxPadCount]; + // TODO SetPadDeviceId(padIndex, const char* id); + + /// Initializes the device. + /** + * Instantiate the device using InputManager::CreateDevice(). + * + * \param manager The input manager this device is managed by. + * \param device The ID of this device. + */ + InputDevicePad(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Shuts down the device. + ~InputDevicePad(); + + /// Returns DT_PAD. + DeviceType GetType() const { return DT_PAD; } + DeviceVariant GetVariant() const; + const char* GetTypeName() const { return "pad"; } + bool IsValidButtonId(DeviceButtonId deviceButton) const; + + size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + size_t GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const; + ButtonType GetButtonType(DeviceButtonId deviceButton) const; + DeviceButtonId GetButtonByName(const char* name) const; + + InputState* GetNextInputState(); + + /// Enables the rumble feature of the pad. + /** + * \param leftMotor Speed of the left motor, between 0.0 and 1.0. + * \param rightMotor Speed of the right motor, between 0.0 and 1.0. + * \return true if rumble was enabled successfully, false otherwise. + */ + bool Vibrate(float leftMotor, float rightMotor); + + /// Returns the platform-specific implementation of this device. + InputDevicePadImpl* GetPimpl() { return impl_; } + +protected: + void InternalUpdate(InputDeltaState* delta); + + DeviceState InternalGetState() const; + +private: + InputDevicePadImpl* impl_; + +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputDeviceTouch.h b/lib/include/gainput/GainputInputDeviceTouch.h new file mode 100644 index 00000000..80f0dddb --- /dev/null +++ b/lib/include/gainput/GainputInputDeviceTouch.h @@ -0,0 +1,107 @@ + +#ifndef GAINPUTINPUTDEVICETOUCH_H_ +#define GAINPUTINPUTDEVICETOUCH_H_ + +namespace gainput +{ + +/// All valid device inputs for InputDeviceTouch. +enum TouchButton +{ + Touch0Down, + Touch0X, + Touch0Y, + Touch0Pressure, + Touch1Down, + Touch1X, + Touch1Y, + Touch1Pressure, + Touch2Down, + Touch2X, + Touch2Y, + Touch2Pressure, + Touch3Down, + Touch3X, + Touch3Y, + Touch3Pressure, + Touch4Down, + Touch4X, + Touch4Y, + Touch4Pressure, + Touch5Down, + Touch5X, + Touch5Y, + Touch5Pressure, + Touch6Down, + Touch6X, + Touch6Y, + Touch6Pressure, + Touch7Down, + Touch7X, + Touch7Y, + Touch7Pressure, + TouchCount_ +}; + + + +class InputDeviceTouchImpl; + +/// A touch input device. +/** + * This input device provides support for touch screen/surface devices. The valid device buttons are defined + * in the ::TouchButton enum. For each touch point, there is a boolean input (Touch*Down) showing if the + * touch point is active, two float inputs (Touch*X, Touch*Y) showing the touch point's position, and a + * float input (Touch*Pressure) showing the touch's pressure. + * + * Not all touch points must be numbered consecutively, i.e. point #2 may be down even though #0 and #1 are not. + * + * The number of simultaneously possible touch points is implementaion-dependent. + * + * This device is implemented on Android NDK. + */ +class GAINPUT_LIBEXPORT InputDeviceTouch : public InputDevice +{ +public: + /// Initializes the device. + /** + * Instantiate the device using InputManager::CreateDevice(). + * + * \param manager The input manager this device is managed by. + * \param device The ID of this device. + */ + InputDeviceTouch(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Shuts down the device. + ~InputDeviceTouch(); + + /// Returns DT_TOUCH. + DeviceType GetType() const { return DT_TOUCH; } + DeviceVariant GetVariant() const; + const char* GetTypeName() const { return "touch"; } + bool IsValidButtonId(DeviceButtonId deviceButton) const; + + size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + size_t GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const; + ButtonType GetButtonType(DeviceButtonId deviceButton) const; + DeviceButtonId GetButtonByName(const char* name) const; + + InputState* GetNextInputState(); + + /// Returns the platform-specific implementation of this device. + InputDeviceTouchImpl* GetPimpl() { return impl_; } + +protected: + void InternalUpdate(InputDeltaState* delta); + + DeviceState InternalGetState() const; + +private: + InputDeviceTouchImpl* impl_; + +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputListener.h b/lib/include/gainput/GainputInputListener.h new file mode 100644 index 00000000..1aeef0ee --- /dev/null +++ b/lib/include/gainput/GainputInputListener.h @@ -0,0 +1,77 @@ + +#ifndef GAINPUTINPUTLISTENER_H_ +#define GAINPUTINPUTLISTENER_H_ + +namespace gainput +{ + +/// Listener interface that allows to receive notifications when device button states change. +class GAINPUT_LIBEXPORT InputListener +{ +public: + virtual ~InputListener() { } + + /// Called when a bool-type button state changes. + /** + * \param device The input device's ID the state change occurred on. + * \param deviceButton The ID of the device button that changed. + * \param oldValue Previous state of the button. + * \param newValue New state of the button. + * \return true if the button may be processed by listeners with a lower priority, false otherwise. + */ + virtual bool OnDeviceButtonBool(DeviceId device, DeviceButtonId deviceButton, bool oldValue, bool newValue) { GAINPUT_UNUSED(device); GAINPUT_UNUSED(deviceButton); GAINPUT_UNUSED(oldValue); GAINPUT_UNUSED(newValue); return true; } + + /// Called when a float-type button state changes. + /** + * \param device The input device's ID the state change occurred on. + * \param deviceButton The ID of the device button that changed. + * \param oldValue Previous state of the button. + * \param newValue New state of the button. + * \return true if the button may be processed by listeners with a lower priority, false otherwise. + */ + virtual bool OnDeviceButtonFloat(DeviceId device, DeviceButtonId deviceButton, float oldValue, float newValue) { GAINPUT_UNUSED(device); GAINPUT_UNUSED(deviceButton); GAINPUT_UNUSED(oldValue); GAINPUT_UNUSED(newValue); return true; } + + /// Returns the priority which influences the order in which listeners are called by InputManager. + /** + * \sa InputManager::ReorderListeners() + */ + virtual int GetPriority() const { return 0; } +}; + + +/// Listener interface that allows to receive notifications when user button states change. +class GAINPUT_LIBEXPORT MappedInputListener +{ +public: + virtual ~MappedInputListener() { } + + /// Called when a bool-type button state changes. + /** + * \param userButton The user button's ID. + * \param oldValue Previous state of the button. + * \param newValue New state of the button. + * \return true if the button may be processed by listeners with a lower priority, false otherwise. + */ + virtual bool OnUserButtonBool(UserButtonId userButton, bool oldValue, bool newValue) { GAINPUT_UNUSED(userButton); GAINPUT_UNUSED(oldValue); GAINPUT_UNUSED(newValue); return true; } + + /// Called when a float-type button state changes. + /** + * \param userButton The user button's ID. + * \param oldValue Previous state of the button. + * \param newValue New state of the button. + * \return true if the button may be processed by listeners with a lower priority, false otherwise. + */ + virtual bool OnUserButtonFloat(UserButtonId userButton, float oldValue, float newValue) { GAINPUT_UNUSED(userButton); GAINPUT_UNUSED(oldValue); GAINPUT_UNUSED(newValue); return true; } + + /// Returns the priority which influences the order in which listeners are called by InputMap. + /** + * \sa InputMap::ReorderListeners() + */ + virtual int GetPriority() const { return 0; } + +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputManager.h b/lib/include/gainput/GainputInputManager.h new file mode 100644 index 00000000..129afa69 --- /dev/null +++ b/lib/include/gainput/GainputInputManager.h @@ -0,0 +1,331 @@ + +#ifndef GAINPUTINPUTMANAGER_H_ +#define GAINPUTINPUTMANAGER_H_ + + +namespace gainput +{ + +/// Manages all input devices and some other helpful stuff. +/** + * This manager takes care of all device-related things. Normally, you should only need one that contains + * all your input devices. + * + * After instantiating an InputManager, you have to call SetDisplaySize(). You should also create some + * input devices using the template function CreateDevice() which returns the device ID that is needed + * to do anything further with the device (for example, see InputMap). + * + * The manager has to be updated every frame by calling Update(). Depending on the platform, + * you may have to call another function as part of your message handling code (see HandleMessage(), HandleInput()). + * + * Note that destruction of an InputManager invalidates all input maps based on it and all devices created + * through it. + */ +class GAINPUT_LIBEXPORT InputManager +{ +public: + /// Initializes the manager. + /** + * Further initialization is typically necessary. + * \param useSystemTime Specifies if the GetTime() function uses system time or the time + * supplied to Update(uint64_t). + * \param allocator The memory allocator to be used for all allocations. + * \see SetDisplaySize + * \see GetTime + */ + InputManager(bool useSystemTime = true, Allocator& allocator = GetDefaultAllocator()); + + /// Destructs the manager. + ~InputManager(); + + /// Sets the window resolution. + /** + * Informs the InputManager and its devices of the size of the window that is used for + * receiving inputs. For example, the size is used to to normalize mouse inputs. + */ + void SetDisplaySize(int width, int height) { displayWidth_ = width; displayHeight_ = height; } + +#if defined(GAINPUT_PLATFORM_LINUX) + /// [LINUX ONLY] Lets the InputManager handle the given X event. + /** + * Call this function for event types MotionNotify, ButtonPress, + * ButtonRelease, KeyPress, KeyRelease. + */ + void HandleEvent(XEvent& event); +#endif +#if defined(GAINPUT_PLATFORM_WIN) + /// [WINDOWS ONLY] Lets the InputManager handle the given Windows message. + /** + * Call this function for message types WM_CHAR, WM_KEYDOWN, WM_KEYUP, + * WM_SYSKEYDOWN, WM_SYSKEYUP, WM_?BUTTON*, WM_MOUSEMOVE, WM_MOUSEWHEEL. + */ + void HandleMessage(const MSG& msg); +#endif +#if defined(GAINPUT_PLATFORM_ANDROID) + /// [ANDROID ONLY] Lets the InputManager handle the given input event. + int32_t HandleInput(AInputEvent* event); + + struct DeviceInput + { + InputDevice::DeviceType deviceType; + unsigned deviceIndex; + ButtonType buttonType; + DeviceButtonId buttonId; + union + { + float f; + bool b; + } value; + }; + void HandleDeviceInput(DeviceInput const& input); +#endif + + /// Updates the input state, call this every frame. + /** + * If the InputManager was initialized with `useSystemTime` set to `false`, you have to + * call Update(uint64_t) instead. + * \see GetTime + */ + void Update(); + + /// Updates the input state and the manager's time, call this every frame. + /** + * Updates the time returned by GetTime() and then calls the regular Update(). This function + * should only be called if the InputManager was initialized with `useSystemTime` + * set to `false`. + * \param deltaTime The provided must be in milliseconds. + * \see Update + * \see GetTime + */ + void Update(uint64_t deltaTime); + + /// Returns the allocator to be used for memory allocations. + Allocator& GetAllocator() const { return allocator_; } + + /// Returns a monotonic time in milliseconds. + uint64_t GetTime() const; + + /// Creates an input device and registers it with the manager. + /** + * \tparam T The input device class, muste be derived from InputDevice. + * \param variant Requests the specified device variant. Note that this is only + * a request. Another implementation variant of the device may silently be instantiated + * instead. To determine what variant was instantiated, call InputDevice::GetVariant(). + * \return The ID of the newly created input device. + */ + template DeviceId CreateDevice(unsigned index = InputDevice::AutoIndex, InputDevice::DeviceVariant variant = InputDevice::DV_STANDARD); + /// Creates an input device, registers it with the manager and returns it. + /** + * \tparam T The input device class, muste be derived from InputDevice. + * \param variant Requests the specified device variant. Note that this is only + * a request. Another implementation variant of the device may silently be instantiated + * instead. To determine what variant was instantiated, call InputDevice::GetVariant(). + * \return The newly created input device. + */ + template T* CreateAndGetDevice(unsigned index = InputDevice::AutoIndex, InputDevice::DeviceVariant variant = InputDevice::DV_STANDARD); + /// Returns the input device with the given ID. + /** + * \return The input device or 0 if it doesn't exist. + */ + InputDevice* GetDevice(DeviceId deviceId); + /// Returns the input device with the given ID. + /** + * \return The input device or 0 if it doesn't exist. + */ + const InputDevice* GetDevice(DeviceId deviceId) const; + /// Returns the ID of the device with the given type and index. + /** + * \param typeName The name of the device type. Should come from InputDevice::GetTypeName(). + * \param index The index of the device. Should come from InputDevice::GetIndex(). + * \return The device's ID or InvalidDeviceId if no matching device exists. + */ + DeviceId FindDeviceId(const char* typeName, unsigned index) const; + /// Returns the ID of the device with the given type and index. + /** + * \param type The device type. Should come from InputDevice::GetType(). + * \param index The index of the device. Should come from InputDevice::GetIndex(). + * \return The device's ID or InvalidDeviceId if no matching device exists. + */ + DeviceId FindDeviceId(InputDevice::DeviceType type, unsigned index) const; + + typedef HashMap DeviceMap; + /// Iterator over all registered devices. + typedef DeviceMap::iterator iterator; + /// Const iterator over all registered devices. + typedef DeviceMap::const_iterator const_iterator; + + /// Returns the begin iterator over all registered devices. + iterator begin() { return devices_.begin(); } + /// Returns the end iterator over all registered devices. + iterator end() { return devices_.end(); } + /// Returns the begin iterator over all registered devices. + const_iterator begin() const { return devices_.begin(); } + /// Returns the end iterator over all registered devices. + const_iterator end() const { return devices_.end(); } + + /// Registers a listener to be notified when a button state changes. + /** + * If there are listeners registered, all input devices will have to record their state changes. This incurs extra runtime costs. + */ + ListenerId AddListener(InputListener* listener); + /// De-registers the given listener. + void RemoveListener(ListenerId listenerId); + /// Sorts the list of listeners which controls the order in which listeners are called. + /** + * The order of listeners may be important as the functions being called to notify a listener of a state change can control if + * the state change will be passed to any consequent listeners. Call this function whenever listener priorites have changed. It + * is automatically called by AddListener() and RemoveListener(). + */ + void ReorderListeners(); + + /// Checks if any button on any device is down. + /** + * \param[out] outButtons An array with maxButtonCount fields to receive the device buttons that are down. + * \param maxButtonCount The number of fields in outButtons. + * \return The number of device buttons written to outButtons. + */ + size_t GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + /// Returns the number of devices with the given type. + unsigned GetDeviceCountByType(InputDevice::DeviceType type) const; + /// Returns the graphical display's width in pixels. + int GetDisplayWidth() const { return displayWidth_; } + /// Returns the graphical display's height in pixels. + int GetDisplayHeight() const { return displayHeight_; } + + /// Registers a modifier that will be called after devices have been updated. + ModifierId AddDeviceStateModifier(DeviceStateModifier* modifier); + /// De-registers the given modifier. + void RemoveDeviceStateModifier(ModifierId modifierId); + + void EnqueueConcurrentChange(InputDevice& device, InputState& state, InputDeltaState* delta, DeviceButtonId buttonId, bool value); + void EnqueueConcurrentChange(InputDevice& device, InputState& state, InputDeltaState* delta, DeviceButtonId buttonId, float value); + + /// [IN dev BUILDS ONLY] Connect to a remote host to send device state changes to. + void ConnectForStateSync(const char* ip, unsigned port); + /// [IN dev BUILDS ONLY] Initiate sending of device state changes to the given device. + void StartDeviceStateSync(DeviceId deviceId); + + /// Enable/disable debug rendering of input devices. + void SetDebugRenderingEnabled(bool enabled); + /// Returns true if debug rendering is enabled, false otherwise. + bool IsDebugRenderingEnabled() const { return debugRenderingEnabled_; } + /// Sets the debug renderer to be used if debug rendering is enabled. + void SetDebugRenderer(DebugRenderer* debugRenderer); + /// Returns the previously set debug renderer. + DebugRenderer* GetDebugRenderer() const { return debugRenderer_; } + +private: + Allocator& allocator_; + + DeviceMap devices_; + unsigned nextDeviceId_; + + HashMap listeners_; + unsigned nextListenerId_; + Array sortedListeners_; + + HashMap modifiers_; + unsigned nextModifierId_; + + InputDeltaState* deltaState_; + + uint64_t currentTime_; + struct Change + { + InputDevice* device; + InputState* state; + InputDeltaState* delta; + DeviceButtonId buttonId; + ButtonType type; + union + { + bool b; + float f; + }; + }; + + GAINPUT_CONC_QUEUE(Change) concurrentInputs_; + + int displayWidth_; + int displayHeight_; + bool useSystemTime_; + + bool debugRenderingEnabled_; + DebugRenderer* debugRenderer_; + + void DeviceCreated(InputDevice* device); + + // Do not copy. + InputManager(const InputManager &); + InputManager& operator=(const InputManager &); + +}; + + +template +inline +DeviceId +InputManager::CreateDevice(unsigned index, InputDevice::DeviceVariant variant) +{ + T* device = allocator_.New(*this, nextDeviceId_, index, variant); + devices_[nextDeviceId_] = device; + DeviceCreated(device); + return nextDeviceId_++; +} + +template +inline +T* +InputManager::CreateAndGetDevice(unsigned index, InputDevice::DeviceVariant variant) +{ + T* device = allocator_.New(*this, nextDeviceId_, index, variant); + devices_[nextDeviceId_] = device; + ++nextDeviceId_; + DeviceCreated(device); + return device; +} + +inline +InputDevice* +InputManager::GetDevice(DeviceId deviceId) +{ + DeviceMap::iterator it = devices_.find(deviceId); + if (it == devices_.end()) + { + return 0; + } + return it->second; +} + +inline +const InputDevice* +InputManager::GetDevice(DeviceId deviceId) const +{ + DeviceMap::const_iterator it = devices_.find(deviceId); + if (it == devices_.end()) + { + return 0; + } + return it->second; +} + + +/// Interface for modifiers that change device input states after they have been updated. +class DeviceStateModifier +{ +public: + /// Called after non-dependent devices have been updated. + /** + * This function is called by InputManager::Update() after InputDevice::Update() has been + * called on all registered devices that have InputDevice::IsLateUpdate() return \c false. + * + * \param delta All device state changes should be registered with this delta state, may be 0. + */ + virtual void Update(InputDeltaState* delta) = 0; +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputMap.h b/lib/include/gainput/GainputInputMap.h new file mode 100644 index 00000000..d09e45ef --- /dev/null +++ b/lib/include/gainput/GainputInputMap.h @@ -0,0 +1,195 @@ + +#ifndef GAINPUTINPUTMAP_H_ +#define GAINPUTINPUTMAP_H_ + +namespace gainput +{ + +class UserButton; + +/// Type for filter functions that can be used to filter mapped float inputs. +typedef float (*FilterFunc_T)(float const value, void* userData); + +/// Maps user buttons to device buttons. +/** + * This is the interface that should be used to get input. You can have several maps that are used + * simultaneously or use different ones depending on game state. The user button IDs have to be unique per input map. + * + * InputMap uses the provided InputManager to get devices inputs and process them into user-mapped inputs. After creating + * an InputMap, you should map some device buttons to user buttons (using MapBool() or MapFloat()). User buttons are identified + * by an ID provided by you. In order to ensure their uniqueness, it's a good idea to define an enum containing all your user buttons + * for any given InputMap. It's of course possible to map multiple different device button to one user button. + * + * After a user button has been mapped, you can query its state by calling one of the several GetBool* and GetFloat* functions. The + * result will depend on the mapped device button(s) and the policy (set using SetUserButtonPolicy()). + */ +class GAINPUT_LIBEXPORT InputMap +{ +public: + /// Initializes the map. + /** + * \param manager The input manager used to get device inputs. + * \param name The name for the input map (optional). If a name is provided, it is copied to an internal buffer. + * \param allocator The allocator to be used for all memory allocations. + */ + InputMap(InputManager& manager, const char* name = 0, Allocator& allocator = GetDefaultAllocator()); + /// Unitializes the map. + ~InputMap(); + + /// Clears all mapped inputs. + void Clear(); + + /// Returns the input manager this input map uses. + const InputManager& GetManager() const { return manager_; } + /// Returns the map's name, if any. + /** + * \return The map's name or 0 if no name was set. + */ + const char* GetName() const { return name_; } + /// Returns the map's auto-generated ID (that should not be used outside of the library). + unsigned GetId() const { return id_; } + + /// Maps a bool-type button. + /** + * \param userButton The user ID for this mapping. + * \param device The device's ID of the device button to be mapped. + * \param deviceButton The ID of the device button to be mapped. + * \return true if the mapping was created. + */ + bool MapBool(UserButtonId userButton, DeviceId device, DeviceButtonId deviceButton); + /// Maps a float-type button, possibly to a custom range. + /** + * \param userButton The user ID for this mapping. + * \param device The device's ID of the device button to be mapped. + * \param deviceButton The ID of the device button to be mapped. + * \param min Optional minimum value of the mapped button. + * \param max Optional maximum value of the mapped button. + * \param filterFunc Optional filter functions that modifies the device button value. + * \param filterUserData Optional user data pointer that is passed to filterFunc. + * \return true if the mapping was created. + */ + bool MapFloat(UserButtonId userButton, DeviceId device, DeviceButtonId deviceButton, + float min = 0.0f, float max = 1.0f, + FilterFunc_T filterFunc = 0, void* filterUserData = 0); + /// Removes all mappings for the given user button. + void Unmap(UserButtonId userButton); + /// Returns if the given user button has any mappings. + bool IsMapped(UserButtonId userButton) const; + + /// Gets all device buttons mapped to the given user button. + /** + * \param userButton The user button ID of the button to return all mappings for. + * \param[out] outButtons An array with maxButtonCount fields to receive the device buttons that are mapped. + * \param maxButtonCount The number of fields in outButtons. + * \return The number of device buttons written to outButtons. + */ + size_t GetMappings(UserButtonId userButton, DeviceButtonSpec* outButtons, size_t maxButtonCount) const; + + /// Policy for how multiple device buttons are summarized in one user button. + enum UserButtonPolicy + { + UBP_FIRST_DOWN, ///< The first device buttons that is down (or not 0.0f) determines the result. + UBP_MAX, ///< The maximum of all device button states is the result. + UBP_MIN, ///< The minimum of all device button states is the result. + UBP_AVERAGE ///< The average of all device button states is the result. + }; + /// Sets how a user button handles inputs from multiple device buttons. + /** + * \return true if the policy was set, false otherwise (i.e. the user button doesn't exist). + */ + bool SetUserButtonPolicy(UserButtonId userButton, UserButtonPolicy policy); + + /// Sets a dead zone for a float-type button. + /** + * If a dead zone is set for a button anything less or equal to the given value will be treated + * as 0.0f. The absolute input value is used in order to determine if the input value falls within the dead + * zone (i.e. with a dead zone of 0.2f, both -0.1f and 0.1f will result in 0.0f). + * + * \param userButton The user button's ID. + * \param deadZone The dead zone to be set. + * \return true if the dead zone was set, false otherwise (i.e. the user button doesn't exist). + */ + bool SetDeadZone(UserButtonId userButton, float deadZone); + + /// Returns the bool state of a user button. + bool GetBool(UserButtonId userButton) const; + /// Returns if the user button is newly down. + bool GetBoolIsNew(UserButtonId userButton) const; + /// Returns the bool state of a user button from the previous frame. + bool GetBoolPrevious(UserButtonId userButton) const; + /// Returns if the user button has been released. + bool GetBoolWasDown(UserButtonId userButton) const; + + /// Returns the float state of a user button. + float GetFloat(UserButtonId userButton) const; + /// Returns the float state of a user button from the previous frame. + float GetFloatPrevious(UserButtonId userButton) const; + /// Returns the delta between the previous and the current frame of the float state of the given user button. + float GetFloatDelta(UserButtonId userButton) const; + + /// Gets the name of the device button mapped to the given user button. + /** + * \param userButton ID of the user button. + * \param buffer A char-buffer to receive the button name. + * \param bufferLength Length of the buffer receiving the button name in bytes. + * \return The number of bytes written to buffer (includes the trailing \0). + */ + size_t GetUserButtonName(UserButtonId userButton, char* buffer, size_t bufferLength) const; + + /// Returns the user button ID the given device button is mapped to. + /** + * This function iterates over all mapped buttons and therefore shouldn't be used in a performance critical + * situation. + * + * \param device The device's ID of the device button to be checked. + * \param deviceButton The ID of the device button to be checked. + * \return The user button ID the device button is mapped to or InvalidDeviceButtonId if the device button is not mapped. + */ + UserButtonId GetUserButtonId(DeviceId device, DeviceButtonId deviceButton) const; + + /// Registers a listener to be notified when a button state changes. + /** + * If there are listeners registered, all input devices will have to record their state changes. This incurs extra runtime costs. + */ + ListenerId AddListener(MappedInputListener* listener); + /// De-registers the given listener. + void RemoveListener(ListenerId listenerId); + /// Sorts the list of listeners which controls the order in which listeners are called. + /** + * The order of listeners may be important as the functions being called to notify a listener of a state change can control if + * the state change will be passed to any consequent listeners. Call this function whenever listener priorites have changed. It + * is automatically called by AddListener() and RemoveListener(). + */ + void ReorderListeners(); + +private: + InputManager& manager_; + char* name_; + unsigned id_; + Allocator& allocator_; + + typedef HashMap UserButtonMap; + UserButtonMap userButtons_; + UserButtonId nextUserButtonId_; + + HashMap listeners_; + Array sortedListeners_; + unsigned nextListenerId_; + InputListener* managerListener_; + ListenerId managerListenerId_; + + float GetFloatState(UserButtonId userButton, bool previous) const; + + UserButton* GetUserButton(UserButtonId userButton); + const UserButton* GetUserButton(UserButtonId userButton) const; + + // Do not copy. + InputMap(const InputMap &); + InputMap& operator=(const InputMap &); + +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputInputState.h b/lib/include/gainput/GainputInputState.h new file mode 100644 index 00000000..d412d287 --- /dev/null +++ b/lib/include/gainput/GainputInputState.h @@ -0,0 +1,60 @@ + +#ifndef GAINPUTINPUTSTATE_H_ +#define GAINPUTINPUTSTATE_H_ + +namespace gainput +{ + +/// State of an input device. +class GAINPUT_LIBEXPORT InputState +{ +public: + /// Initializes the state. + /** + * \param allocator The allocator to be used for all memory allocations. + * \param buttonCount The maximum number of device buttons. + */ + InputState(Allocator& allocator, unsigned int buttonCount); + /// Unitializes the state. + ~InputState(); + + /// Returns the number of buttons in this state. + /** + * Note that not all buttons may be valid. + * + * \sa InputDevice::IsValidButtonId() + */ + unsigned GetButtonCount() const { return buttonCount_; } + + /// Returns the bool state of the given device button. + bool GetBool(DeviceButtonId buttonId) const { return buttons_[buttonId].b; } + /// Sets the bool state of the given device button. + void Set(DeviceButtonId buttonId, bool value) { buttons_[buttonId].b = value; } + /// Returns the float state of the given device button. + float GetFloat(DeviceButtonId buttonId) const { return buttons_[buttonId].f; } + /// Sets the float state of the given device button. + void Set(DeviceButtonId buttonId, float value) { buttons_[buttonId].f = value; } + + /// Sets the states of all buttons in this input state to the states of all buttons in the given input state. + InputState& operator=(const InputState& other); + +private: + Allocator& allocator_; + unsigned int buttonCount_; + + struct Button + { + union + { + bool b; + float f; + }; + }; + + Button* buttons_; +}; + +} + +#endif + diff --git a/lib/include/gainput/GainputIos.h b/lib/include/gainput/GainputIos.h new file mode 100644 index 00000000..9b6af6bd --- /dev/null +++ b/lib/include/gainput/GainputIos.h @@ -0,0 +1,26 @@ + +#ifndef GAINPUTIOS_H_ +#define GAINPUTIOS_H_ + +#import + +namespace gainput +{ + class InputManager; +} + +/// [IOS ONLY] UIKit view that captures touch inputs. +/** + * In order to enable touch input on iOS devices (i.e. make the InputDeviceTouch work), + * an instance of this view has to be attached as a subview to the view of your application. + * Note that, for touches to work in portrait and landscape mode, the subview has to be + * rotated correctly with its parent. + */ +@interface GainputView : UIView + +- (id)initWithFrame:(CGRect)frame inputManager:(gainput::InputManager&)inputManager; + +@end + +#endif + diff --git a/lib/include/gainput/GainputLog.h b/lib/include/gainput/GainputLog.h new file mode 100644 index 00000000..1151dfa1 --- /dev/null +++ b/lib/include/gainput/GainputLog.h @@ -0,0 +1,39 @@ + +#ifndef GAINPUT_LOG_H_ +#define GAINPUT_LOG_H_ + +#include + +#if defined(GAINPUT_PLATFORM_LINUX) + +#if defined(GAINPUT_DEBUG) || defined(GAINPUT_DEV) + #include + #define GAINPUT_LOG(...) printf(__VA_ARGS__); +#endif + +#elif defined(GAINPUT_PLATFORM_WIN) + +#if defined(GAINPUT_DEBUG) || defined(GAINPUT_DEV) + #include + #include + #define GAINPUT_LOG(...) { char buf[1024]; sprintf(buf, __VA_ARGS__); OutputDebugStringA(buf); } +#endif + +#elif defined(GAINPUT_PLATFORM_ANDROID) + +#if defined(GAINPUT_DEBUG) || defined(GAINPUT_DEV) + #include + #define GAINPUT_LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "gainput", __VA_ARGS__)) +#endif + +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + #include + #define GAINPUT_LOG(...) printf(__VA_ARGS__); +#endif + +#ifndef GAINPUT_LOG +#define GAINPUT_LOG(...) +#endif + +#endif + diff --git a/lib/include/gainput/GainputMapFilters.h b/lib/include/gainput/GainputMapFilters.h new file mode 100644 index 00000000..196fdfc9 --- /dev/null +++ b/lib/include/gainput/GainputMapFilters.h @@ -0,0 +1,17 @@ + +#ifndef GAINPUTMAPFILTERS_H_ +#define GAINPUTMAPFILTERS_H_ + +namespace gainput +{ + +/// Inverts the given input value in the range [-1.0f, 1.0f]. +GAINPUT_LIBEXPORT float InvertSymmetricInput(float const value, void*); + +/// Inverts the given input value in the range [0.0f, 1.0f]. +GAINPUT_LIBEXPORT float InvertInput(float const value, void*); + +} + +#endif + diff --git a/lib/include/gainput/gainput.h b/lib/include/gainput/gainput.h new file mode 100644 index 00000000..ea67c47d --- /dev/null +++ b/lib/include/gainput/gainput.h @@ -0,0 +1,193 @@ +/** + * Gainput - C++ input library for games. + * + * Copyright (c) 2013-2017 Johannes Kuhlmann. + * Licensed under the MIT license. See LICENSE file. + */ + +#ifndef GAINPUT_H_ +#define GAINPUT_H_ + +#if defined(__ANDROID__) || defined(ANDROID) + #define GAINPUT_PLATFORM_ANDROID + #define GAINPUT_LIBEXPORT +#elif defined(__linux) || defined(__linux__) || defined(linux) || defined(LINUX) + #define GAINPUT_PLATFORM_LINUX + #define GAINPUT_LIBEXPORT +#elif defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) + #define GAINPUT_PLATFORM_WIN + #if defined(GAINPUT_LIB_DYNAMIC) + #define GAINPUT_LIBEXPORT __declspec(dllexport) + #elif defined(GAINPUT_LIB_DYNAMIC_USE) + #define GAINPUT_LIBEXPORT __declspec(dllimport) + #else + #define GAINPUT_LIBEXPORT + #endif +#elif defined(__APPLE__) + #define GAINPUT_LIBEXPORT + #include + #if TARGET_OS_TV + #define GAINPUT_PLATFORM_TVOS + #elif TARGET_OS_IPHONE + #define GAINPUT_PLATFORM_IOS + #elif TARGET_OS_MAC + #define GAINPUT_PLATFORM_MAC + #else + #error Gainput: Unknown/unsupported Apple platform! + #endif +#else + #error Gainput: Unknown/unsupported platform! +#endif + + +//#define GAINPUT_DEBUG +//#define GAINPUT_DEV +#define GAINPUT_ENABLE_ALL_GESTURES +#define GAINPUT_ENABLE_RECORDER +#define GAINPUT_TEXT_INPUT_QUEUE_LENGTH 32 + +#ifdef GAINPUT_ENABLE_CONCURRENCY +#define MOODYCAMEL_EXCEPTIONS_DISABLED +#include "concurrentqueue.h" +#define GAINPUT_CONC_QUEUE(TYPE) moodycamel::ConcurrentQueue +#define GAINPUT_CONC_CONSTRUCT(queue) queue() +#define GAINPUT_CONC_ENQUEUE(queue, obj) queue.enqueue(obj) +#define GAINPUT_CONC_DEQUEUE(queue, obj) queue.try_dequeue(obj) +#else +#define GAINPUT_CONC_QUEUE(TYPE) gainput::Array +#define GAINPUT_CONC_CONSTRUCT(queue) queue(allocator) +#define GAINPUT_CONC_ENQUEUE(queue, obj) queue.push_back(obj) +#define GAINPUT_CONC_DEQUEUE(queue, obj) (!queue.empty() ? (obj = queue[queue.size()-1], queue.pop_back(), true) : false) +#endif + +#include +#include +#include + +#define GAINPUT_ASSERT assert +#define GAINPUT_UNUSED(x) (void)(x) + +#if defined(GAINPUT_PLATFORM_LINUX) + +#include +#include + +union _XEvent; +typedef _XEvent XEvent; + +#elif defined(GAINPUT_PLATFORM_WIN) + +#include + +typedef struct tagMSG MSG; + +namespace gainput +{ + typedef unsigned __int8 uint8_t; + typedef __int8 int8_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int64 uint64_t; +} + +#elif defined(GAINPUT_PLATFORM_ANDROID) + +#include +#include +struct AInputEvent; + +#endif + + + +/// Contains all Gainput related classes, types, and functions. +namespace gainput +{ + +/// ID of an input device. +typedef unsigned int DeviceId; +/// ID of a specific button unique to an input device. +typedef unsigned int DeviceButtonId; + +/// Describes a device button on a specific device. +struct DeviceButtonSpec +{ + /// ID of the input device. + DeviceId deviceId; + /// ID of the button on the given input device. + DeviceButtonId buttonId; +}; + +/// ID of a user-defined, mapped button. +typedef unsigned int UserButtonId; +/// ID of an input listener. +typedef unsigned int ListenerId; +/// ID of a device state modifier. +typedef unsigned int ModifierId; + +/// An invalid device ID. +static const DeviceId InvalidDeviceId = -1; +/// An invalid device button ID. +static const DeviceButtonId InvalidDeviceButtonId = -1; +/// An invalid user button ID. +static const UserButtonId InvalidUserButtonId = -1; + +/// Returns the name of the library, should be "Gainput". +const char* GetLibName(); +/// Returns the version number of the library. +uint32_t GetLibVersion(); +/// Returns the version number of the library as a printable string. +const char* GetLibVersionString(); + +class InputDeltaState; +class InputListener; +class InputManager; +class DebugRenderer; +class DeviceStateModifier; + +template T Abs(T a) { return a < T() ? -a : a; } + +/// Switches the library's internal development server to HTTP mode. +/** + * When the server is in HTTP mode, it is possible to control touch + * input using an external HTML page that connects to the library + * via HTTP. + * + * The HTML page(s) can be found under `tools/html5client/` and should + * be placed on an HTTP server that can be reached from the touch device + * that should send touch events to the library. The touch device then + * in turn connects to the library's internal HTTP server and periodically + * sends touch input information. + * + * The pages can also be found hosted here: + * http://gainput.johanneskuhlmann.de/html5client/ + */ +void DevSetHttp(bool enable); +} + +#define GAINPUT_VER_MAJOR_SHIFT 16 +#define GAINPUT_VER_GET_MAJOR(ver) (ver >> GAINPUT_VER_MAJOR_SHIFT) +#define GAINPUT_VER_GET_MINOR(ver) (ver & (uint32_t(-1) >> GAINPUT_VER_MAJOR_SHIFT)) + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#endif + diff --git a/lib/include/gainput/gestures/GainputButtonStickGesture.h b/lib/include/gainput/gestures/GainputButtonStickGesture.h new file mode 100644 index 00000000..5cac7a3d --- /dev/null +++ b/lib/include/gainput/gestures/GainputButtonStickGesture.h @@ -0,0 +1,47 @@ + +#ifndef GAINPUTBUTTONSTICKGESTURE_H_ +#define GAINPUTBUTTONSTICKGESTURE_H_ + +#ifdef GAINPUT_ENABLE_BUTTON_STICK_GESTURE + +namespace gainput +{ + +/// Buttons provided by the ButtonStickGesture. +enum ButtonStickAction +{ + ButtonStickAxis +}; + + +class GAINPUT_LIBEXPORT ButtonStickGesture : public InputGesture +{ +public: + /// Initializes the gesture. + ButtonStickGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~ButtonStickGesture(); + + /// Sets up the gesture for operation with the given axes and buttons. + void Initialize(DeviceId negativeAxisDevice, DeviceButtonId negativeAxis, + DeviceId positiveAxisDevice, DeviceButtonId positiveAxis); + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == ButtonStickAxis; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_UNUSED(deviceButton); GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return BT_FLOAT; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + DeviceButtonSpec negativeAxis_; + DeviceButtonSpec positiveAxis_; + +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/gestures/GainputDoubleClickGesture.h b/lib/include/gainput/gestures/GainputDoubleClickGesture.h new file mode 100644 index 00000000..45ef61f2 --- /dev/null +++ b/lib/include/gainput/gestures/GainputDoubleClickGesture.h @@ -0,0 +1,99 @@ + +#ifndef GAINPUTDOUBLECLICKGESTURE_H_ +#define GAINPUTDOUBLECLICKGESTURE_H_ + +#ifdef GAINPUT_ENABLE_DOUBLE_CLICK_GESTURE + +namespace gainput +{ + +/// Buttons provided by the DoubleClickGesture. +enum DoubleClickAction +{ + DoubleClickTriggered ///< The button triggered by double-clicking. +}; + +/// A double-click gesture. +/** + * This gesture implements the classic double-click functionality. Its only device button ::DoubleClickTriggered is + * true for one frame after the specified button has been active for a specified number of times in a given + * time frame. It's also possible to disallow the pointer from travelling too far between and during clicks. + * + * After instantiating the gesture like any other input device, call one of the Initialize() functions to properly + * set it up. + * + * In order for this gesture to be available, Gainput must be built with \c GAINPUT_ENABLE_ALL_GESTURES or + * \c GAINPUT_ENABLE_DOUBLE_CLICK_GESTURE defined. + * + * \sa Initialize + * \sa SetClicksTargetCount + * \sa InputManager::CreateDevice + */ +class GAINPUT_LIBEXPORT DoubleClickGesture : public InputGesture +{ +public: + /// Initializes the gesture. + DoubleClickGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~DoubleClickGesture(); + + /// Sets up the gesture for operation without position checking. + /** + * \param actionButtonDevice ID of the input device containing the action button. + * \param actionButton ID of the device button to be used as the action button. + * \param timeSpan Allowed time between clicks in milliseconds. + */ + void Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, uint64_t timeSpan = 300); + /// Sets up the gesture for operation with position checking, i.e. the user may not move the mouse too far between clicks. + /** + * \param actionButtonDevice ID of the input device containing the action button. + * \param actionButton ID of the device button to be used as the action button. + * \param xAxisDevice ID of the input device containing the X coordinate of the pointer. + * \param xAxis ID of the device button/axis to be used for the X coordinate of the pointer. + * \param xTolerance The amount the pointer may travel in the X coordinate to still be valid. + * \param yAxisDevice ID of the input device containing the Y coordinate of the pointer. + * \param yAxis ID of the device button/axis to be used for the Y coordinate of the pointer. + * \param yTolerance The amount the pointer may travel in the Y coordinate to still be valid. + * \param timeSpan Allowed time between clicks in milliseconds. + */ + void Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, float xTolerance, + DeviceId yAxisDevice, DeviceButtonId yAxis, float yTolerance, + uint64_t timeSpan = 300); + + /// Sets the number of clicks to trigger an action. + /** + * \param count The number of clicks that will trigger this gesture; the default is 2, i.e. double-click. + */ + void SetClicksTargetCount(unsigned count) { clicksTargetCount_ = count; } + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == DoubleClickTriggered; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_UNUSED(deviceButton); GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return BT_BOOL; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + DeviceButtonSpec actionButton_; + DeviceButtonSpec xAxis_; + float xTolerance_; + DeviceButtonSpec yAxis_; + float yTolerance_; + + uint64_t timeSpan_; + uint64_t firstClickTime_; + + float firstClickX_; + float firstClickY_; + + unsigned clicksRegistered_; + unsigned clicksTargetCount_; +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/gestures/GainputGestures.h b/lib/include/gainput/gestures/GainputGestures.h new file mode 100644 index 00000000..5ebfd12c --- /dev/null +++ b/lib/include/gainput/gestures/GainputGestures.h @@ -0,0 +1,69 @@ + +#ifndef GAINPUTGESTURES_H_ +#define GAINPUTGESTURES_H_ + +#ifdef GAINPUT_ENABLE_ALL_GESTURES +#define GAINPUT_ENABLE_BUTTON_STICK_GESTURE +#define GAINPUT_ENABLE_DOUBLE_CLICK_GESTURE +#define GAINPUT_ENABLE_HOLD_GESTURE +#define GAINPUT_ENABLE_PINCH_GESTURE +#define GAINPUT_ENABLE_ROTATE_GESTURE +#define GAINPUT_ENABLE_SIMULTANEOUSLY_DOWN_GESTURE +#define GAINPUT_ENABLE_TAP_GESTURE +#endif + + +namespace gainput +{ + +/// Base class for all input gestures. +/** + * Input gestures are a way to process basic input data into more complex input data. For example, + * multiple buttons may be interpreted over time to form a new button. A very simple gesture would the + * ubiquitous double-click. + * + * Mainly for consistency and convenience reasons, all gestures should derive from this class though it's not + * strictly necessary (deriving from InputDevice would suffice). + * + * Input gestures are basically just input devices that don't get their data from some hardware device + * but from other input devices instead. Therefore gestures must also be created by calling + * InputManager::CreateDevice() or InputManager::CreateAndGetDevice(). Most gestures require further + * initialization which is done by calling one of their \c Initialize() member functions. After that, + * they should be good to go and their buttons can be used like any other input device button, i.e. + * they can be mapped to some user button. + * + * Gestures can be excluded from compilation if they are not required. In order to include all gestures + * \c GAINPUT_ENABLE_ALL_GESTURES should be defined in \c gainput.h . This define must be present when + * the library is built, otherwise the actual functionality won't be present. Similarly, there is one + * define for each gesture. The names of these are documented in the descriptions of the + * individual gesture classes. If no such define is defined, no gesture will be included. + */ +class GAINPUT_LIBEXPORT InputGesture : public InputDevice +{ +public: + /// Returns DT_GESTURE. + DeviceType GetType() const { return DT_GESTURE; } + const char* GetTypeName() const { return "gesture"; } + bool IsLateUpdate() const { return true; } + +protected: + /// Gesture base constructor. + InputGesture(InputManager& manager, DeviceId device, unsigned index) : InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_GESTURE) : 0) { } + + DeviceState InternalGetState() const { return DS_OK; } + +}; + +} + + +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/lib/include/gainput/gestures/GainputHoldGesture.h b/lib/include/gainput/gestures/GainputHoldGesture.h new file mode 100644 index 00000000..ea9c919d --- /dev/null +++ b/lib/include/gainput/gestures/GainputHoldGesture.h @@ -0,0 +1,94 @@ + +#ifndef GAINPUTHOLDGESTURE_H_ +#define GAINPUTHOLDGESTURE_H_ + +#ifdef GAINPUT_ENABLE_HOLD_GESTURE + +namespace gainput +{ + +/// Buttons provided by the HoldGesture. +enum HoldAction +{ + HoldTriggered ///< The button that triggers after holding for the given time. +}; + +/// A hold-to-trigger gesture. +/** + * This gesture, mainly meant for touch devices, triggers after the specified button has been down for at least + * the specified amount of time. Its button ::HoldTriggered is then either active for one frame or as long as + * the source button is down. + * + * After instantiating the gesture like any other input device, call one of the Initialize() functions to properly + * set it up. + * + * In order for this gesture to be available, Gainput must be built with \c GAINPUT_ENABLE_ALL_GESTURES or + * \c GAINPUT_ENABLE_HOLD_GESTURE defined. + * + * \sa Initialize + * \sa InputManager::CreateDevice + */ +class GAINPUT_LIBEXPORT HoldGesture : public InputGesture +{ +public: + /// Initializes the gesture. + HoldGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~HoldGesture(); + + /// Sets up the gesture for operation without position checking. + /** + * \param actionButtonDevice ID of the input device containing the action button. + * \param actionButton ID of the device button to be used as the action button. + * \param oneShot Specifies if the gesture triggers only once after the given time or if it triggers as long as the source button is down. + * \param timeSpan Time in milliseconds the user needs to hold in order to trigger the gesture. + */ + void Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, bool oneShot = true, uint64_t timeSpan = 800); + /// Sets up the gesture for operation with position checking, i.e. the user may not move the mouse too far while holding. + /** + * \param actionButtonDevice ID of the input device containing the action button. + * \param actionButton ID of the device button to be used as the action button. + * \param xAxisDevice ID of the input device containing the X coordinate of the pointer. + * \param xAxis ID of the device button/axis to be used for the X coordinate of the pointer. + * \param xTolerance The amount the pointer may travel in the X coordinate to still be valid. + * \param yAxisDevice ID of the input device containing the Y coordinate of the pointer. + * \param yAxis ID of the device button/axis to be used for the Y coordinate of the pointer. + * \param yTolerance The amount the pointer may travel in the Y coordinate to still be valid. + * \param oneShot Specifies if the gesture triggers only once after the given time or if it triggers as long as the source button is down. + * \param timeSpan Time in milliseconds the user needs to hold in order to trigger the gesture. + */ + void Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, float xTolerance, + DeviceId yAxisDevice, DeviceButtonId yAxis, float yTolerance, + bool oneShot = true, + uint64_t timeSpan = 800); + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == HoldTriggered; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_UNUSED(deviceButton); GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return BT_BOOL; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + DeviceButtonSpec actionButton_; + DeviceButtonSpec xAxis_; + float xTolerance_; + DeviceButtonSpec yAxis_; + float yTolerance_; + + bool oneShot_; + bool oneShotReset_; + uint64_t timeSpan_; + uint64_t firstDownTime_; + + float firstDownX_; + float firstDownY_; +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/gestures/GainputPinchGesture.h b/lib/include/gainput/gestures/GainputPinchGesture.h new file mode 100644 index 00000000..a6326c12 --- /dev/null +++ b/lib/include/gainput/gestures/GainputPinchGesture.h @@ -0,0 +1,86 @@ + +#ifndef GAINPUTPINCHGESTURE_H_ +#define GAINPUTPINCHGESTURE_H_ + +#ifdef GAINPUT_ENABLE_PINCH_GESTURE + +namespace gainput +{ + +/// Buttons provided by the PinchGesture. +enum PinchAction +{ + PinchTriggered, ///< The button that triggers when both pinch buttons are down. + PinchScale ///< The current pinch scale value if pinching is active. +}; + +/// A multi-touch pinch-to-scale gesture. +/** + * This gesture, mainly meant for multi-touch devices, triggers (::PinchTriggered is down) when both specified + * source buttons are down. It will then determine the distance between the two 2D touch points and report + * any change in distance as a factor of the initial distance using ::PinchScale. + * + * After instantiating the gesture like any other input device, call Initialize() to properly + * set it up. + * + * In order for this gesture to be available, Gainput must be built with \c GAINPUT_ENABLE_ALL_GESTURES or + * \c GAINPUT_ENABLE_PINCH_GESTURE defined. + * + * \sa Initialize + * \sa InputManager::CreateDevice + */ +class GAINPUT_LIBEXPORT PinchGesture : public InputGesture +{ +public: + /// Initializes the gesture. + PinchGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~PinchGesture(); + + /// Sets up the gesture for operation with the given axes and buttons. + /** + * \param downDevice ID of the input device containing the first touch button. + * \param downButton ID of the device button to be used as the first touch button. + * \param xAxisDevice ID of the input device containing the X coordinate of the first touch point. + * \param xAxis ID of the device button/axis to be used for the X coordinate of the first touch point. + * \param yAxisDevice ID of the input device containing the Y coordinate of the first touch point. + * \param yAxis ID of the device button/axis to be used for the Y coordinate of the first touch point. + * \param down2Device ID of the input device containing the second touch button. + * \param downButton2 ID of the device button to be used as the second touch button. + * \param xAxis2Device ID of the input device containing the X coordinate of the second touch point. + * \param xAxis2 ID of the device button/axis to be used for the X coordinate of the second touch point. + * \param yAxis2Device ID of the input device containing the Y coordinate of the second touch point. + * \param yAxis2 ID of the device button/axis to be used for the Y coordinate of the second touch point. + */ + void Initialize(DeviceId downDevice, DeviceButtonId downButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, + DeviceId yAxisDevice, DeviceButtonId yAxis, + DeviceId down2Device, DeviceButtonId downButton2, + DeviceId xAxis2Device, DeviceButtonId xAxis2, + DeviceId yAxis2Device, DeviceButtonId yAxis2); + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == PinchTriggered || deviceButton == PinchScale; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return deviceButton == PinchTriggered ? BT_BOOL : BT_FLOAT; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + DeviceButtonSpec downButton_; + DeviceButtonSpec xAxis_; + DeviceButtonSpec yAxis_; + DeviceButtonSpec downButton2_; + DeviceButtonSpec xAxis2_; + DeviceButtonSpec yAxis2_; + + bool pinching_; + float initialDistance_; +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/gestures/GainputRotateGesture.h b/lib/include/gainput/gestures/GainputRotateGesture.h new file mode 100644 index 00000000..dfcce26c --- /dev/null +++ b/lib/include/gainput/gestures/GainputRotateGesture.h @@ -0,0 +1,87 @@ + +#ifndef GAINPUTROTATEGESTURE_H_ +#define GAINPUTROTATEGESTURE_H_ + +#ifdef GAINPUT_ENABLE_ROTATE_GESTURE + +namespace gainput +{ + +/// Buttons provided by the RotateGesture. +enum RotateAction +{ + RotateTriggered, ///< The button that triggers when both rotate buttons are down. + RotateAngle ///< The current rotation angle in radians if rotation is triggered (::RotateTriggered). +}; + +/// A multi-touch rotate gesture. +/** + * This gesture, mainly meant for multi-touch devices, triggers (::RotateTriggered is down) when both specified + * source buttons are down. It then determines the angle between the two specified 2D touch points and reports any + * change in angle using ::RotateAngle. The initial angle between the two points is defined as no rotation. + * + * After instantiating the gesture like any other input device, call Initialize() to properly + * set it up. + * + * In order for this gesture to be available, Gainput must be built with \c GAINPUT_ENABLE_ALL_GESTURES or + * \c GAINPUT_ENABLE_ROTATE_GESTURE defined. + * + * \sa Initialize + * \sa InputManager::CreateDevice + */ +class GAINPUT_LIBEXPORT RotateGesture : public InputGesture +{ +public: + /// Initializes the gesture. + RotateGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~RotateGesture(); + + /// Sets up the gesture for operation with the given axes and buttons. + /** + * \param downDevice ID of the input device containing the first touch button. + * \param downButton ID of the device button to be used as the first touch button. + * \param xAxisDevice ID of the input device containing the X coordinate of the first touch point. + * \param xAxis ID of the device button/axis to be used for the X coordinate of the first touch point. + * \param yAxisDevice ID of the input device containing the Y coordinate of the first touch point. + * \param yAxis ID of the device button/axis to be used for the Y coordinate of the first touch point. + * \param down2Device ID of the input device containing the second touch button. + * \param downButton2 ID of the device button to be used as the second touch button. + * \param xAxis2Device ID of the input device containing the X coordinate of the second touch point. + * \param xAxis2 ID of the device button/axis to be used for the X coordinate of the second touch point. + * \param yAxis2Device ID of the input device containing the Y coordinate of the second touch point. + * \param yAxis2 ID of the device button/axis to be used for the Y coordinate of the second touch point. + */ + void Initialize(DeviceId downDevice, DeviceButtonId downButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, + DeviceId yAxisDevice, DeviceButtonId yAxis, + DeviceId down2Device, DeviceButtonId downButton2, + DeviceId xAxis2Device, DeviceButtonId xAxis2, + DeviceId yAxis2Device, DeviceButtonId yAxis2); + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == RotateTriggered || deviceButton == RotateAngle; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return deviceButton == RotateTriggered ? BT_BOOL : BT_FLOAT; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + DeviceButtonSpec downButton_; + DeviceButtonSpec xAxis_; + DeviceButtonSpec yAxis_; + DeviceButtonSpec downButton2_; + DeviceButtonSpec xAxis2_; + DeviceButtonSpec yAxis2_; + + bool rotating_; + float initialAngle_; + +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/gestures/GainputSimultaneouslyDownGesture.h b/lib/include/gainput/gestures/GainputSimultaneouslyDownGesture.h new file mode 100644 index 00000000..f0ef1aeb --- /dev/null +++ b/lib/include/gainput/gestures/GainputSimultaneouslyDownGesture.h @@ -0,0 +1,66 @@ + +#ifndef GAINPUTSIMULTANEOUSLYDOWNGESTURE_H_ +#define GAINPUTSIMULTANEOUSLYDOWNGESTURE_H_ + +#ifdef GAINPUT_ENABLE_SIMULTANEOUSLY_DOWN_GESTURE + +namespace gainput +{ + +/// Buttons provided by the SimultaneouslyDownGesture. +enum SimultaneouslyDownAction +{ + SimultaneouslyDownTriggered ///< The button triggered by double-clicking. +}; + +/// A gesture that tracks if a number of buttons is down simultaneously. +/** + * This gesture can be used to detect if multiple buttons are down at the same time. Its only + * device button ::SimultaneouslyDownTriggered is true while all buttons provided through AddButton() + * are down. + * + * After instantiating the gesture like any other input device, call AddButton() as often as necessary + * to properly set it up. + * + * In order for this gesture to be available, Gainput must be built with \c GAINPUT_ENABLE_ALL_GESTURES or + * \c GAINPUT_ENABLE_SIMULTANEOUSLY_DOWN_GESTURE defined. + * + * \sa AddButton + * \sa InputManager::CreateDevice + */ +class GAINPUT_LIBEXPORT SimultaneouslyDownGesture : public InputGesture +{ +public: + /// Initializes the gesture. + SimultaneouslyDownGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~SimultaneouslyDownGesture(); + + /// Adds the given button as a button to check. + /** + * \param device ID of the input device containing the button to be checked. + * \param button ID of the device button to be checked. + */ + void AddButton(DeviceId device, DeviceButtonId button); + + /// Removes all buttons previously registered through AddButton(). + void ClearButtons(); + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == SimultaneouslyDownTriggered; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_UNUSED(deviceButton); GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return BT_BOOL; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + Array buttons_; + +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/gestures/GainputTapGesture.h b/lib/include/gainput/gestures/GainputTapGesture.h new file mode 100644 index 00000000..3e32f184 --- /dev/null +++ b/lib/include/gainput/gestures/GainputTapGesture.h @@ -0,0 +1,66 @@ + +#ifndef GAINPUTTAPGESTURE_H_ +#define GAINPUTTAPGESTURE_H_ + +#ifdef GAINPUT_ENABLE_TAP_GESTURE + +namespace gainput +{ + +/// Buttons provided by the TapGesture. +enum TapAction +{ + TapTriggered ///< The button that is triggered by tapping. +}; + +/// A tap-to-trigger gesture. +/** + * This gesture, mainly meant for touch devices, triggers after the specified button has been down and released + * during the specified time frame. If the button is down for a longer time, no action is triggered. + * + * After instantiating the gesture like any other input device, call Initialize() to properly + * set it up. + * + * In order for this gesture to be available, Gainput must be built with \c GAINPUT_ENABLE_ALL_GESTURES or + * \c GAINPUT_ENABLE_TAP_GESTURE defined. + * + * \sa Initialize + * \sa InputManager::CreateDevice + */ +class GAINPUT_LIBEXPORT TapGesture : public InputGesture +{ +public: + /// Initializes the gesture. + TapGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant); + /// Uninitializes the gesture. + ~TapGesture(); + + /// Sets up the gesture. + /** + * \param actionButtonDevice ID of the input device containing the action button. + * \param actionButton ID of the device button to be used as the action button. + * \param timeSpan Time in milliseconds the user may hold at most. + */ + void Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, uint64_t timeSpan = 500); + + bool IsValidButtonId(DeviceButtonId deviceButton) const { return deviceButton == TapTriggered; } + + ButtonType GetButtonType(DeviceButtonId deviceButton) const { GAINPUT_UNUSED(deviceButton); GAINPUT_ASSERT(IsValidButtonId(deviceButton)); return BT_BOOL; } + +protected: + void InternalUpdate(InputDeltaState* delta); + +private: + DeviceButtonSpec actionButton_; + + uint64_t timeSpan_; + uint64_t firstDownTime_; + +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/recorder/GainputInputPlayer.h b/lib/include/gainput/recorder/GainputInputPlayer.h new file mode 100644 index 00000000..f7a5674c --- /dev/null +++ b/lib/include/gainput/recorder/GainputInputPlayer.h @@ -0,0 +1,65 @@ + +#ifndef GAINPUTINPUTPLAYER_H_ +#define GAINPUTINPUTPLAYER_H_ + +#ifdef GAINPUT_ENABLE_RECORDER + +namespace gainput +{ + +/// Plays back a previously recorded sequence of device state changes. +/** + * In order for input recording to be available, Gainput must have been built with + * \c GAINPUT_ENABLE_RECORDER defined. + */ +class GAINPUT_LIBEXPORT InputPlayer : public DeviceStateModifier +{ +public: + /// Initializes the player. + /** + * \param manager The manager to receive the device state changes. + * \param recording The recording to play, may be 0. + */ + InputPlayer(InputManager& manager, InputRecording* recording = 0); + /// Destructs the player. + ~InputPlayer(); + + /// Updates the player, called internally from InputManager::Update(). + void Update(InputDeltaState* delta); + + /// Starts the playback. + /** + * A recording must have been provided before doing this, either through the + * constructor or SetRecording(). + */ + void Start(); + /// Stops the Playback. + void Stop(); + /// Returns if the player is currently playing. + bool IsPlaying() const { return isPlaying_; } + + /// Sets the recording to play. + void SetRecording(InputRecording* recording); + /// Returns the currently set recording. + InputRecording* GetRecording() { return recording_; } + /// Returns the currently set recording. + const InputRecording* GetRecording() const { return recording_; } + +private: + InputManager& manager_; + + bool isPlaying_; + InputRecording* recording_; + uint64_t startTime_; + + Array devicesToReset_; + + ModifierId playingModifierId_; +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/recorder/GainputInputRecorder.h b/lib/include/gainput/recorder/GainputInputRecorder.h new file mode 100644 index 00000000..a2deba4a --- /dev/null +++ b/lib/include/gainput/recorder/GainputInputRecorder.h @@ -0,0 +1,73 @@ + +#ifndef GAINPUTINPUTRECORDER_H_ +#define GAINPUTINPUTRECORDER_H_ + +#ifdef GAINPUT_ENABLE_RECORDER + +namespace gainput +{ + +/// Records a sequence of button state changes. +/** + * In order for input recording to be available, Gainput must have been built with + * \c GAINPUT_ENABLE_RECORDER defined. + */ +class GAINPUT_LIBEXPORT InputRecorder +{ +public: + /// Initializes the recorder. + /** + * \param manager The InputManager to receive button state changes from. + */ + InputRecorder(InputManager& manager); + /// Destructs the recorder. + ~InputRecorder(); + + /// Starts recording. + /** + * Also clears the InputRecording that is being recorded to so that it's not possible + * to resume recording after stopping to record. + */ + void Start(); + /// Stops recording. + void Stop(); + /// Returns if the recorder is currently recording. + bool IsRecording() const { return isRecording_; } + + /// Adds a device to record the button state changes of. + /** + * If no device is set, all devices are recorded. + * \param device The ID of the device to record. + */ + void AddDeviceToRecord(DeviceId device) { recordedDevices_[device] = true; } + /// Returns if the given device should be recorded. + /** + * \param device The ID of the device to check. + */ + bool IsDeviceToRecord(DeviceId device) { return recordedDevices_.empty() || recordedDevices_.count(device) > 0; } + + /// Returns the recording that is being recorded to, may be 0. + InputRecording* GetRecording() { return recording_; } + /// Returns the recording that is being recorded to, may be 0. + const InputRecording* GetRecording() const { return recording_; } + /// Returns the time the recording was started. + uint64_t GetStartTime() const { return startTime_; } + +private: + InputManager& manager_; + + bool isRecording_; + InputListener* recordingListener_; + ListenerId recordingListenerId_; + InputRecording* recording_; + uint64_t startTime_; + HashMap recordedDevices_; + +}; + +} + +#endif + +#endif + diff --git a/lib/include/gainput/recorder/GainputInputRecording.h b/lib/include/gainput/recorder/GainputInputRecording.h new file mode 100644 index 00000000..5bd59445 --- /dev/null +++ b/lib/include/gainput/recorder/GainputInputRecording.h @@ -0,0 +1,125 @@ + +#ifndef GAINPUTINPUTRECORDING_H_ +#define GAINPUTINPUTRECORDING_H_ + +#ifdef GAINPUT_ENABLE_RECORDER + +namespace gainput +{ + +/// A single recorded change for a device button. +struct GAINPUT_LIBEXPORT RecordedDeviceButtonChange +{ + /// The time at which the change occurred. + uint64_t time; + /// The ID of the device owning the button that changed. + DeviceId deviceId; + /// The ID of the button that changed. + DeviceButtonId buttonId; + + union + { + /// If the button's type is ::BT_BOOL, this contains the new value. + bool b; + /// If the button's type is ::BT_FLOAT, this contains the new value. + float f; + }; +}; + +/// A recorded sequence of input changes. +/** + * The recording can be recorded to, played, or serialized/deserialized. + * + * In order for input recording to be available, Gainput must have been built with + * \c GAINPUT_ENABLE_RECORDER defined. + * + * \sa InputPlayer + * \sa InputRecorder + */ +class GAINPUT_LIBEXPORT InputRecording +{ +public: + /// Initializes the recording in an empty state. + /** + * \param allocator The allocator to be used for all memory allocations. + */ + InputRecording(Allocator& allocator = GetDefaultAllocator()); + /// Initializes the recording from the given serialized data. + /** + * The recording is reconstructed from a previously serialized recording obtained through + * GetSerialized(). + * + * \param manager Used to resolve device and button references in the recording. + * \param data The serialized recording as obtained from GetSerialized(). + * \param size The length of the serialized recording as obtained from GetSerializedSize(). + * \param allocator The allocator to be used for all memory allocations. + */ + InputRecording(InputManager& manager, void* data, size_t size, Allocator& allocator = GetDefaultAllocator()); + + /// Appends a device button change to the recording. + /** + * The changes must be added in chronological order, i.e. time must always greater or equal to the time + * AddChange() was last called with. + * + * \param time The time elapsed before the change occurred. + * \param deviceId The ID of the device owning the button that changed. + * \param buttonId The ID of the button that changed. + * \param value The new value of the button. + */ + void AddChange(uint64_t time, DeviceId deviceId, DeviceButtonId buttonId, bool value); + /// Appends a device button change to the recording. + /** + * The changes must be added in chronological order, i.e. time must always greater or equal to the time + * AddChange() was last called with. + * + * \param time The time elapsed before the change occurred. + * \param deviceId The ID of the device owning the button that changed. + * \param buttonId The ID of the button that changed. + * \param value The new value of the button. + */ + void AddChange(uint64_t time, DeviceId deviceId, DeviceButtonId buttonId, float value); + + /// Removes all state changes. + void Clear(); + + /// Gets the next button change before and including the given time and returns it. + /** + * \param time The time up to which to return changes. + * \param[out] outChange The change properties will be written to this if this function returns true. + * \return true if a change matching the given time was found, false otherwise. + */ + bool GetNextChange(uint64_t time, RecordedDeviceButtonChange& outChange); + /// Resets the playback position. + /** + * After calling this function, GetNextChange() will return changes from the beginning of the recorded + * sequence of changes again. + */ + void Reset() { position_ = 0; } + + /// Returns what time frame this recording spans. + uint64_t GetDuration() const; + + /// Returns the size required to serialize this recording. + size_t GetSerializedSize() const; + /// Serializes this recording to the given buffer. + /** + * This function serializes this recording so that it can be saved and read back in and deserialized later. + * + * \param manager Used to resolve device and button references in the recording. + * \param data A buffer of (at least) a size as returned by GetSerializedSize(). + */ + void GetSerialized(InputManager& manager, void* data) const; + +private: + Array changes_; + + /// The position in changes_ for reading. + unsigned position_; +}; + +} + +#endif + +#endif + diff --git a/lib/java/com/example/gainput/gainput/BasicActivity.java b/lib/java/com/example/gainput/gainput/BasicActivity.java new file mode 100644 index 00000000..60545dab --- /dev/null +++ b/lib/java/com/example/gainput/gainput/BasicActivity.java @@ -0,0 +1,85 @@ +package com.example.gainput.gainput; + +import android.app.Activity; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; + +import java.util.Timer; +import java.util.TimerTask; + +import de.johanneskuhlmann.gainput.Gainput; + +public class BasicActivity extends Activity +{ + private Gainput gainputHandler; + private Timer timer; + + public static native void nativeOnCreate(); + public static native void nativeOnUpdate(); + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + System.loadLibrary("basicsample"); + gainputHandler = new Gainput(getApplicationContext()); + nativeOnCreate(); + + getWindow().getDecorView().findViewById(android.R.id.content).addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + gainputHandler.viewWidth = v.getWidth(); + gainputHandler.viewHeight = v.getHeight(); + } + }); + + timer = new Timer(); + TimerTask t = new TimerTask() { + int sec = 0; + @Override + public void run() { + nativeOnUpdate(); + } + }; + timer.scheduleAtFixedRate(t, 500, 33); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) + { + gainputHandler.handleKeyEvent(event); + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) + { + if (gainputHandler.handleTouchEvent(event)) + { + return true; + } + return super.onTouchEvent(event); + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) + { + if (gainputHandler.handleMotionEvent(event)) + { + return true; + } + return super.onGenericMotionEvent(event); + } + + @Override + public boolean dispatchKeyEvent (KeyEvent event) + { + if (gainputHandler.handleKeyEvent(event)) + { + return true; + } + return super.dispatchKeyEvent(event); + } +} diff --git a/lib/java/de/johanneskuhlmann/gainput/Gainput.java b/lib/java/de/johanneskuhlmann/gainput/Gainput.java new file mode 100644 index 00000000..ec3117bf --- /dev/null +++ b/lib/java/de/johanneskuhlmann/gainput/Gainput.java @@ -0,0 +1,385 @@ +package de.johanneskuhlmann.gainput; + +import android.content.Context; +import android.hardware.input.InputManager; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.util.HashMap; +import java.util.Map; + +public class Gainput implements InputManager.InputDeviceListener +{ + + // Must have same order/values as the respective enum in GainputInputDevicePad.h + enum PadButton + { + PadButtonLeftStickX, + PadButtonLeftStickY, + PadButtonRightStickX, + PadButtonRightStickY, + PadButtonAxis4, // L2/Left trigger + PadButtonAxis5, // R2/Right trigger + PadButtonAxis6, + PadButtonAxis7, + PadButtonAxis8, + PadButtonAxis9, + PadButtonAxis10, + PadButtonAxis11, + PadButtonAxis12, + PadButtonAxis13, + PadButtonAxis14, + PadButtonAxis15, + PadButtonAxis16, + PadButtonAxis17, + PadButtonAxis18, + PadButtonAxis19, + PadButtonAxis20, + PadButtonAxis21, + PadButtonAxis22, + PadButtonAxis23, + PadButtonAxis24, + PadButtonAxis25, + PadButtonAxis26, + PadButtonAxis27, + PadButtonAxis28, + PadButtonAxis29, + PadButtonAxis30, + PadButtonAxis31, + PadButtonAccelerationX, + PadButtonAccelerationY, + PadButtonAccelerationZ, + PadButtonGravityX, + PadButtonGravityY, + PadButtonGravityZ, + PadButtonGyroscopeX, + PadButtonGyroscopeY, + PadButtonGyroscopeZ, + PadButtonMagneticFieldX, + PadButtonMagneticFieldY, + PadButtonMagneticFieldZ, + PadButtonStart, + PadButtonSelect, + PadButtonLeft, + PadButtonRight, + PadButtonUp, + PadButtonDown, + PadButtonA, // Cross + PadButtonB, // Circle + PadButtonX, // Square + PadButtonY, // Triangle + PadButtonL1, + PadButtonR1, + PadButtonL2, + PadButtonR2, + PadButtonL3, // Left thumb + PadButtonR3, // Right thumb + PadButtonHome, // PS button + PadButton17, + PadButton18, + PadButton19, + PadButton20, + PadButton21, + PadButton22, + PadButton23, + PadButton24, + PadButton25, + PadButton26, + PadButton27, + PadButton28, + PadButton29, + PadButton30, + PadButton31, + PadButtonMax_ + } + + // Must have same order/values as the respective enum in GainputInputDevice.h + enum DeviceType + { + DT_MOUSE, ///< A mouse/cursor input device featuring one pointer. + DT_KEYBOARD, ///< A keyboard input device. + DT_PAD, ///< A joypad/gamepad input device. + DT_TOUCH, ///< A touch-sensitive input device supporting multiple simultaneous pointers. + DT_BUILTIN, ///< Any controls directly built into the device that also contains the screen. + DT_REMOTE, ///< A generic networked input device. + DT_GESTURE, ///< A gesture input device, building on top of other input devices. + DT_CUSTOM, ///< A custom, user-created input device. + DT_COUNT ///< The count of input device types. + } + + public static native void nativeOnInputBool(int deviceType, int deviceIndex, int buttonId, boolean value); + public static native void nativeOnInputFloat(int deviceType, int deviceIndex, int buttonId, float value); + public static native void nativeOnDeviceChanged(int deviceId, boolean available); + + public float viewWidth = 1.0f; + public float viewHeight = 1.0f; + + private float getRealX(float x) + { + return (x/viewWidth); + } + + private float getRealY(float y) + { + return (y/viewHeight); + } + + public Gainput(Context context) + { + inputManager_ = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + + if (inputManager_ != null) + { + inputManager_.registerInputDeviceListener(this, null); + } + + int[] deviceIds = InputDevice.getDeviceIds(); + for (int deviceId : deviceIds) + { + InputDevice dev = InputDevice.getDevice(deviceId); + int sources = dev.getSources(); + if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + || ((sources & InputDevice.SOURCE_JOYSTICK) + == InputDevice.SOURCE_JOYSTICK)) + { + onInputDeviceAdded(deviceId); + } + } + } + + @Override + protected void finalize() + { + if (inputManager_ != null) + { + inputManager_.unregisterInputDeviceListener(this); + } + } + + private final InputManager inputManager_; + private Map deviceIdMappings_ = new HashMap(); + + private int translateDeviceIdToIndex(int deviceId) + { + Integer index = deviceIdMappings_.get(deviceId); + if (index != null) + { + return index; + } + + // Find the lowest non-used index. + for (int i = 0; i < 1000; ++i) + { + if (!deviceIdMappings_.containsValue(i)) + { + deviceIdMappings_.put(deviceId, i); + return i; + } + } + return 0; + } + + @Override + public void onInputDeviceAdded(int deviceId) + { + nativeOnDeviceChanged(translateDeviceIdToIndex(deviceId), true); + } + + @Override + public void onInputDeviceChanged(int deviceId) + { + } + + @Override + public void onInputDeviceRemoved(int deviceId) + { + int oldDeviceId = translateDeviceIdToIndex(deviceId); + deviceIdMappings_.remove(deviceId); + nativeOnDeviceChanged(oldDeviceId, false); + } + + private void handleAxis(int deviceId, PadButton button, float value) + { + boolean isButton = false; + if (button == PadButton.PadButtonLeft + || button == PadButton.PadButtonUp) + { + if (value < -0.5f) + value = -1.0f; + else + value = 0.0f; + isButton = true; + } + else if (button == PadButton.PadButtonRight + || button == PadButton.PadButtonDown) + { + if (value > 0.5f) + value = 1.0f; + else + value = 0.0f; + isButton = true; + } + + if (isButton) + { + nativeOnInputBool(DeviceType.DT_PAD.ordinal(), deviceId, button.ordinal(), value != 0.0f); + } + else + { + nativeOnInputFloat(DeviceType.DT_PAD.ordinal(), deviceId, button.ordinal(), value); + } + } + + public boolean handleMotionEvent(MotionEvent event) + { + int source = event.getSource(); + if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK + || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + { + int deviceId = translateDeviceIdToIndex(event.getDeviceId()); + handleAxis(deviceId, PadButton.PadButtonLeftStickX, event.getAxisValue(MotionEvent.AXIS_X)); + handleAxis(deviceId, PadButton.PadButtonLeftStickY, -event.getAxisValue(MotionEvent.AXIS_Y)); + handleAxis(deviceId, PadButton.PadButtonRightStickX, event.getAxisValue(MotionEvent.AXIS_Z)); + handleAxis(deviceId, PadButton.PadButtonRightStickY, -event.getAxisValue(MotionEvent.AXIS_RZ)); + handleAxis(deviceId, PadButton.PadButtonAxis4, event.getAxisValue(MotionEvent.AXIS_LTRIGGER)); + handleAxis(deviceId, PadButton.PadButtonAxis5, event.getAxisValue(MotionEvent.AXIS_RTRIGGER)); + handleAxis(deviceId, PadButton.PadButtonLeft, event.getAxisValue(MotionEvent.AXIS_HAT_X)); + handleAxis(deviceId, PadButton.PadButtonRight, event.getAxisValue(MotionEvent.AXIS_HAT_X)); + handleAxis(deviceId, PadButton.PadButtonUp, event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + handleAxis(deviceId, PadButton.PadButtonDown, event.getAxisValue(MotionEvent.AXIS_HAT_Y)); + return true; + } + return false; + } + + private float getButtonState(KeyEvent event) + { + if (event.getAction() == KeyEvent.ACTION_DOWN) + return 1.0f; + return 0.0f; + } + + private void handleButton(int deviceId, PadButton button, KeyEvent event) + { + float state = getButtonState(event); + nativeOnInputBool(DeviceType.DT_PAD.ordinal(), deviceId, button.ordinal(), state != 0.0f); + } + + public boolean handleKeyEvent(KeyEvent event) + { + int keyCode = event.getKeyCode(); + int source = event.getSource(); + if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK + || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) + { + int deviceId = translateDeviceIdToIndex(event.getDeviceId()); + if (keyCode == KeyEvent.KEYCODE_DPAD_UP) + handleButton(deviceId, PadButton.PadButtonUp, event); + else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) + handleButton(deviceId, PadButton.PadButtonDown, event); + else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) + handleButton(deviceId, PadButton.PadButtonLeft, event); + else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) + handleButton(deviceId, PadButton.PadButtonRight, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_A) + handleButton(deviceId, PadButton.PadButtonA, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_B) + handleButton(deviceId, PadButton.PadButtonB, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_X) + handleButton(deviceId, PadButton.PadButtonX, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_Y) + handleButton(deviceId, PadButton.PadButtonY, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_L1) + handleButton(deviceId, PadButton.PadButtonL1, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_R1) + handleButton(deviceId, PadButton.PadButtonR1, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBL) + handleButton(deviceId, PadButton.PadButtonL3, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_THUMBR) + handleButton(deviceId, PadButton.PadButtonR3, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_SELECT) + handleButton(deviceId, PadButton.PadButtonSelect, event); + else if (keyCode == KeyEvent.KEYCODE_BUTTON_START) + handleButton(deviceId, PadButton.PadButtonStart, event); + else if (keyCode == KeyEvent.KEYCODE_HOME) + handleButton(deviceId, PadButton.PadButtonHome, event); + return true; + } + else if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) + { + boolean down = event.getAction() == KeyEvent.ACTION_DOWN; + nativeOnInputBool(DeviceType.DT_KEYBOARD.ordinal(), 0, event.getKeyCode(), down); + + if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) + { + return false; + } + else + { + return true; + } + } + return false; + + } + + public boolean handleTouchEvent(MotionEvent event) + { + try + { + final int action = event.getAction() & MotionEvent.ACTION_MASK; + final int numberOfPointers = event.getPointerCount(); + + switch (action) + { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + { + for (int i = 0; i < numberOfPointers; ++i) + { + final int pointerId = event.getPointerId(i); + final float x_move = getRealX(event.getX(i)); + final float y_move = getRealY(event.getY(i)); + nativeOnInputBool(DeviceType.DT_TOUCH.ordinal(), 0, 0 + 4 * pointerId, true); + nativeOnInputFloat(DeviceType.DT_TOUCH.ordinal(), 0, 1 + 4 * pointerId, x_move); + nativeOnInputFloat(DeviceType.DT_TOUCH.ordinal(), 0, 2 + 4 * pointerId, y_move); + } + break; + } + case MotionEvent.ACTION_MOVE: + { + for (int i = 0; i < numberOfPointers; ++i) + { + final int pointerId = event.getPointerId(i); + final float x_move = getRealX(event.getX(i)); + final float y_move = getRealY(event.getY(i)); + nativeOnInputFloat(DeviceType.DT_TOUCH.ordinal(), 0, 1 + 4 * pointerId, x_move); + nativeOnInputFloat(DeviceType.DT_TOUCH.ordinal(), 0, 2 + 4 * pointerId, y_move); + } + break; + } + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_UP: + { + for (int i = 0; i < numberOfPointers; ++i) + { + final int pointerId = event.getPointerId(i); + final float x_move = getRealX(event.getX(i)); + final float y_move = getRealY(event.getY(i)); + nativeOnInputBool(DeviceType.DT_TOUCH.ordinal(), 0, 0 + 4 * pointerId, false); + nativeOnInputFloat(DeviceType.DT_TOUCH.ordinal(), 0, 1 + 4 * pointerId, x_move); + nativeOnInputFloat(DeviceType.DT_TOUCH.ordinal(), 0, 2 + 4 * pointerId, y_move); + } + break; + } + } + return true; + } + catch (final Exception ex) + { + ex.printStackTrace(); + return false; + } + } +} diff --git a/lib/source/gainput/GainputAllocator.cpp b/lib/source/gainput/GainputAllocator.cpp new file mode 100644 index 00000000..a7f74923 --- /dev/null +++ b/lib/source/gainput/GainputAllocator.cpp @@ -0,0 +1,58 @@ + +#include + +#include + +namespace gainput +{ + +DefaultAllocator& +GetDefaultAllocator() +{ + static DefaultAllocator da; + return da; +} + + +TrackingAllocator::TrackingAllocator(Allocator& backingAllocator, Allocator& internalAllocator) + : backingAllocator_(backingAllocator), + internalAllocator_(internalAllocator), + allocations_(internalAllocator.New >(internalAllocator)), + allocateCount_(0), + deallocateCount_(0), + allocatedMemory_(0) +{ +} + +TrackingAllocator::~TrackingAllocator() +{ + internalAllocator_.Delete(allocations_); +} + +void* TrackingAllocator::Allocate(size_t size, size_t align) +{ + void* ptr = backingAllocator_.Allocate(size, align); + (*allocations_)[ptr] = size; + ++allocateCount_; + allocatedMemory_ += size; + return ptr; +} + +void TrackingAllocator::Deallocate(void* ptr) +{ + HashMap::iterator it = allocations_->find(ptr); + if (it == allocations_->end()) + { + GAINPUT_LOG("Warning: Trying to deallocate unknown memory block: %p\n", ptr); + } + else + { + allocatedMemory_ -= it->second; + allocations_->erase(it); + } + ++deallocateCount_; + backingAllocator_.Deallocate(ptr); +} + +} + diff --git a/lib/source/gainput/GainputHelpersEvdev.h b/lib/source/gainput/GainputHelpersEvdev.h new file mode 100644 index 00000000..284e5fc2 --- /dev/null +++ b/lib/source/gainput/GainputHelpersEvdev.h @@ -0,0 +1,126 @@ + +#ifndef GAINPUTHELPERSEVDEV_H_ +#define GAINPUTHELPERSEVDEV_H_ + +#include +#include +#include +#include + +#include + +namespace gainput +{ +namespace +{ + +static const unsigned EvdevDeviceCount = 10; +static const char* EvdevDeviceIds[EvdevDeviceCount] = +{ + "/dev/input/event0", + "/dev/input/event1", + "/dev/input/event2", + "/dev/input/event3", + "/dev/input/event4", + "/dev/input/event5", + "/dev/input/event6", + "/dev/input/event7", + "/dev/input/event8", + "/dev/input/event9", +}; + +typedef long BitType; + +#define GAINPUT_BITCOUNT (sizeof(BitType)*8) +#define GAINPUT_BITS(n) ((n/GAINPUT_BITCOUNT)+1) + +bool IsBitSet(const BitType* bits, unsigned bit) +{ + return bool(bits[bit/GAINPUT_BITCOUNT] & (1ul << (bit % GAINPUT_BITCOUNT))); +} + +bool HasEventType(const BitType* bits, unsigned type) +{ + return IsBitSet(bits, type); +} + +bool HasEventCode(const BitType* bits, unsigned code) +{ + return IsBitSet(bits, code); +} + + +class EvdevDevice +{ +public: + EvdevDevice(int fd) : + valid_(false) + { + int rc; + + memset(name_, 0, sizeof(name_)); + rc = ioctl(fd, EVIOCGNAME(sizeof(name_) - 1), name_); + if (rc < 0) + return; + + GAINPUT_LOG("EVDEV Device name: %s\n", name_); + + rc = ioctl(fd, EVIOCGBIT(0, sizeof(bits_)), bits_); + if (rc < 0) + return; + + rc = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBits_)), keyBits_); + if (rc < 0) + return; + + valid_ = true; + } + + bool IsValid() const + { + return valid_; + } + + InputDevice::DeviceType GetDeviceType() const + { + if (HasEventType(bits_, EV_REL) + && HasEventType(bits_, EV_KEY) + && HasEventCode(keyBits_, BTN_LEFT)) + { + GAINPUT_LOG("EVDEV Detected as mouse\n"); + return InputDevice::DT_MOUSE; + } + else if (HasEventType(bits_, EV_KEY) + && HasEventCode(keyBits_, KEY_A) + && HasEventCode(keyBits_, KEY_Q)) + { + GAINPUT_LOG("EVDEV Detected as keyboard\n"); + return InputDevice::DT_KEYBOARD; + } + else if (HasEventType(bits_, EV_ABS) + && HasEventType(bits_, EV_KEY) + && (HasEventCode(keyBits_, BTN_GAMEPAD) + || HasEventCode(keyBits_, BTN_JOYSTICK)) + ) + { + GAINPUT_LOG("EVDEV Detected as pad\n"); + return InputDevice::DT_PAD; + } + + GAINPUT_LOG("EVDEV Unknown device type\n"); + return InputDevice::DT_COUNT; + } + +private: + bool valid_; + char name_[255]; + BitType bits_[GAINPUT_BITS(EV_CNT)]; + BitType keyBits_[GAINPUT_BITS(KEY_CNT)]; +}; + + +} +} + +#endif + diff --git a/lib/source/gainput/GainputInputDeltaState.cpp b/lib/source/gainput/GainputInputDeltaState.cpp new file mode 100644 index 00000000..4c6d3f9d --- /dev/null +++ b/lib/source/gainput/GainputInputDeltaState.cpp @@ -0,0 +1,75 @@ + +#include +#include + + +namespace gainput +{ + +InputDeltaState::InputDeltaState(Allocator& allocator) : + changes_(allocator) +{ +} + +void +InputDeltaState::AddChange(DeviceId device, DeviceButtonId deviceButton, bool oldValue, bool newValue) +{ + Change change; + change.device = device; + change.deviceButton = deviceButton; + change.type = BT_BOOL; + change.oldValue.b = oldValue; + change.newValue.b = newValue; + changes_.push_back(change); +} + +void +InputDeltaState::AddChange(DeviceId device, DeviceButtonId deviceButton, float oldValue, float newValue) +{ + Change change; + change.device = device; + change.deviceButton = deviceButton; + change.type = BT_FLOAT; + change.oldValue.f = oldValue; + change.newValue.f = newValue; + changes_.push_back(change); +} + +void +InputDeltaState::Clear() +{ + changes_.clear(); +} + +void +InputDeltaState::NotifyListeners(Array& listeners) const +{ + for (Array::const_iterator it = changes_.begin(); + it != changes_.end(); + ++it) + { + const Change& change = *it; + for (Array::iterator it2 = listeners.begin(); + it2 != listeners.end(); + ++it2) + { + if (change.type == BT_BOOL) + { + if (!(*it2)->OnDeviceButtonBool(change.device, change.deviceButton, change.oldValue.b, change.newValue.b)) + { + break; + } + } + else if (change.type == BT_FLOAT) + { + if(!(*it2)->OnDeviceButtonFloat(change.device, change.deviceButton, change.oldValue.f, change.newValue.f)) + { + break; + } + } + } + } +} + +} + diff --git a/lib/source/gainput/GainputInputDevice.cpp b/lib/source/gainput/GainputInputDevice.cpp new file mode 100644 index 00000000..b7df8ce6 --- /dev/null +++ b/lib/source/gainput/GainputInputDevice.cpp @@ -0,0 +1,97 @@ + +#include + +namespace gainput +{ + + +InputDevice::InputDevice(InputManager& manager, DeviceId device, unsigned index) : + manager_(manager), + deviceId_(device), + index_(index), + deadZones_(0), + debugRenderingEnabled_(false) +#if defined(GAINPUT_DEV) || defined(GAINPUT_ENABLE_RECORDER) + , synced_(false) +#endif +{ +} + +InputDevice::~InputDevice() +{ + manager_.GetAllocator().Deallocate(deadZones_); +} + +void +InputDevice::Update(InputDeltaState* delta) +{ + *previousState_ = *state_; +#if defined(GAINPUT_DEV) + if (synced_) + { + return; + } +#endif + InternalUpdate(delta); +} + +InputDevice::DeviceState +InputDevice::GetState() const +{ +#if defined(GAINPUT_DEV) + if (synced_) + { + return DS_OK; + } +#endif + return InternalGetState(); +} + +float InputDevice::GetDeadZone(DeviceButtonId buttonId) const +{ + if (!deadZones_ + || !IsValidButtonId(buttonId)) + { + return 0.0f; + } + GAINPUT_ASSERT(buttonId < state_->GetButtonCount()); + return deadZones_[buttonId]; +} + +void InputDevice::SetDeadZone(DeviceButtonId buttonId, float value) +{ + if (!deadZones_) + { + const size_t size = sizeof(float) * state_->GetButtonCount(); + deadZones_ = reinterpret_cast(manager_.GetAllocator().Allocate(size)); + memset(deadZones_, 0, size); + } + GAINPUT_ASSERT(buttonId < state_->GetButtonCount()); + deadZones_[buttonId] = value; +} + +void +InputDevice::SetDebugRenderingEnabled(bool enabled) +{ + debugRenderingEnabled_ = enabled; +} + +size_t +InputDevice::CheckAllButtonsDown(DeviceButtonSpec* outButtons, size_t maxButtonCount, unsigned start, unsigned end) const +{ + size_t buttonsFound = 0; + for (unsigned i = start; i < end && buttonsFound < maxButtonCount; ++i) + { + DeviceButtonId id(i); + if (IsValidButtonId(id) && GetBool(id)) + { + outButtons[buttonsFound].deviceId = deviceId_; + outButtons[buttonsFound].buttonId = id; + ++buttonsFound; + } + } + return buttonsFound; +} + +} + diff --git a/lib/source/gainput/GainputInputManager.cpp b/lib/source/gainput/GainputInputManager.cpp new file mode 100644 index 00000000..5a987124 --- /dev/null +++ b/lib/source/gainput/GainputInputManager.cpp @@ -0,0 +1,617 @@ + + +#include +#include + +#if defined(GAINPUT_PLATFORM_LINUX) +#include +#include +#include "keyboard/GainputInputDeviceKeyboardLinux.h" +#include "mouse/GainputInputDeviceMouseLinux.h" +#elif defined(GAINPUT_PLATFORM_WIN) +#include "keyboard/GainputInputDeviceKeyboardWin.h" +#include "keyboard/GainputInputDeviceKeyboardWinRaw.h" +#include "mouse/GainputInputDeviceMouseWin.h" +#include "mouse/GainputInputDeviceMouseWinRaw.h" +#elif defined(GAINPUT_PLATFORM_ANDROID) +#include +#include +#include "keyboard/GainputInputDeviceKeyboardAndroid.h" +#include "pad/GainputInputDevicePadAndroid.h" +#include "touch/GainputInputDeviceTouchAndroid.h" +static gainput::InputManager* gGainputInputManager; +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) +#include +#include +#endif + +#include + +#include "dev/GainputDev.h" +#include + + +namespace gainput +{ + +InputManager::InputManager(bool useSystemTime, Allocator& allocator) : + allocator_(allocator), + devices_(allocator_), + nextDeviceId_(0), + listeners_(allocator_), + nextListenerId_(0), + sortedListeners_(allocator_), + modifiers_(allocator_), + nextModifierId_(0), + deltaState_(allocator_.New(allocator_)), + currentTime_(0), + GAINPUT_CONC_CONSTRUCT(concurrentInputs_), + displayWidth_(-1), + displayHeight_(-1), + useSystemTime_(useSystemTime), + debugRenderingEnabled_(false), + debugRenderer_(0) +{ + GAINPUT_DEV_INIT(this); +#ifdef GAINPUT_PLATFORM_ANDROID + gGainputInputManager = this; +#endif +} + +InputManager::~InputManager() +{ + allocator_.Delete(deltaState_); + + for (DeviceMap::iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { + allocator_.Delete(it->second); + } + + GAINPUT_DEV_SHUTDOWN(this); +} + +void +InputManager::Update() +{ + Change change; + while (GAINPUT_CONC_DEQUEUE(concurrentInputs_, change)) + { + if (change.type == BT_BOOL) + { + HandleButton(*change.device, *change.state, change.delta, change.buttonId, change.b); + } + else if (change.type == BT_FLOAT) + { + HandleAxis(*change.device, *change.state, change.delta, change.buttonId, change.f); + } + } + + InputDeltaState* ds = listeners_.empty() ? 0 : deltaState_; + + for (DeviceMap::iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { + if (!it->second->IsLateUpdate()) + { + it->second->Update(ds); + } + } + + GAINPUT_DEV_UPDATE(ds); + + for (HashMap::iterator it = modifiers_.begin(); + it != modifiers_.end(); + ++it) + { + it->second->Update(ds); + } + + for (DeviceMap::iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { + if (it->second->IsLateUpdate()) + { + it->second->Update(ds); + } + } + + if (ds) + { + ds->NotifyListeners(sortedListeners_); + ds->Clear(); + } +} + +void +InputManager::Update(uint64_t deltaTime) +{ + GAINPUT_ASSERT(useSystemTime_ == false); + currentTime_ += deltaTime; + Update(); +} + +uint64_t +InputManager::GetTime() const +{ + if (useSystemTime_) + { +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + { + return -1; + } + + uint64_t t = ts.tv_sec*1000ul + ts.tv_nsec/1000000ul; + return t; +#elif defined(GAINPUT_PLATFORM_WIN) + static LARGE_INTEGER perfFreq = { 0 }; + if (perfFreq.QuadPart == 0) + { + QueryPerformanceFrequency(&perfFreq); + GAINPUT_ASSERT(perfFreq.QuadPart != 0); + } + LARGE_INTEGER count; + QueryPerformanceCounter(&count); + double t = 1000.0 * double(count.QuadPart) / double(perfFreq.QuadPart); + return static_cast(t); +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + clock_serv_t cclock; + mach_timespec_t mts; + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + uint64_t t = mts.tv_sec*1000ul + mts.tv_nsec/1000000ul; + return t; +#else +#error Gainput: No time support +#endif +} + else + { + return currentTime_; + } +} + +DeviceId +InputManager::FindDeviceId(const char* typeName, unsigned index) const +{ + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { + if (strcmp(typeName, it->second->GetTypeName()) == 0 + && it->second->GetIndex() == index) + { + return it->first; + } + } + return InvalidDeviceId; +} + +DeviceId +InputManager::FindDeviceId(InputDevice::DeviceType type, unsigned index) const +{ + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { + if (it->second->GetType() == type + && it->second->GetIndex() == index) + { + return it->first; + } + } + return InvalidDeviceId; +} + +ListenerId +InputManager::AddListener(InputListener* listener) +{ + listeners_[nextListenerId_] = listener; + ReorderListeners(); + return nextListenerId_++; +} + +void +InputManager::RemoveListener(ListenerId listenerId) +{ + listeners_.erase(listenerId); + ReorderListeners(); +} + +namespace { +static int CompareListeners(const void* a, const void* b) +{ + const InputListener* listener1 = *reinterpret_cast(a); + const InputListener* listener2 = *reinterpret_cast(b); + return listener2->GetPriority() - listener1->GetPriority(); +} +} + +void +InputManager::ReorderListeners() +{ + sortedListeners_.clear(); + for (HashMap::iterator it = listeners_.begin(); + it != listeners_.end(); + ++it) + { + sortedListeners_.push_back(it->second); + } + + if (sortedListeners_.empty()) + { + return; + } + + qsort(&sortedListeners_[0], + sortedListeners_.size(), + sizeof(InputListener*), + &CompareListeners); +} + +ModifierId +InputManager::AddDeviceStateModifier(DeviceStateModifier* modifier) +{ + modifiers_[nextModifierId_] = modifier; + return nextModifierId_++; +} + +void +InputManager::RemoveDeviceStateModifier(ModifierId modifierId) +{ + modifiers_.erase(modifierId); +} + +size_t +InputManager::GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + size_t buttonsFound = 0; + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end() && maxButtonCount > buttonsFound; + ++it) + { + buttonsFound += it->second->GetAnyButtonDown(outButtons+buttonsFound, maxButtonCount-buttonsFound); + } + return buttonsFound; +} + +unsigned +InputManager::GetDeviceCountByType(InputDevice::DeviceType type) const +{ + unsigned count = 0; + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { + if (it->second->GetType() == type) + { + ++count; + } + } + return count; +} + +void +InputManager::DeviceCreated(InputDevice* device) +{ + GAINPUT_UNUSED(device); + GAINPUT_DEV_NEW_DEVICE(device); +} + +#if defined(GAINPUT_PLATFORM_LINUX) +void +InputManager::HandleEvent(XEvent& event) +{ + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { +#if defined(GAINPUT_DEV) + if (it->second->IsSynced()) + { + continue; + } +#endif + if (it->second->GetType() == InputDevice::DT_KEYBOARD + && it->second->GetVariant() == InputDevice::DV_STANDARD) + { + InputDeviceKeyboard* keyboard = static_cast(it->second); + InputDeviceKeyboardImplLinux* keyboardImpl = static_cast(keyboard->GetPimpl()); + GAINPUT_ASSERT(keyboardImpl); + keyboardImpl->HandleEvent(event); + } + else if (it->second->GetType() == InputDevice::DT_MOUSE + && it->second->GetVariant() == InputDevice::DV_STANDARD) + { + InputDeviceMouse* mouse = static_cast(it->second); + InputDeviceMouseImplLinux* mouseImpl = static_cast(mouse->GetPimpl()); + GAINPUT_ASSERT(mouseImpl); + mouseImpl->HandleEvent(event); + } + } +} +#endif + +#if defined(GAINPUT_PLATFORM_WIN) +void +InputManager::HandleMessage(const MSG& msg) +{ + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { +#if defined(GAINPUT_DEV) + if (it->second->IsSynced()) + { + continue; + } +#endif + if (it->second->GetType() == InputDevice::DT_KEYBOARD) + { + InputDeviceKeyboard* keyboard = static_cast(it->second); + if (it->second->GetVariant() == InputDevice::DV_STANDARD) + { + InputDeviceKeyboardImplWin* keyboardImpl = static_cast(keyboard->GetPimpl()); + GAINPUT_ASSERT(keyboardImpl); + keyboardImpl->HandleMessage(msg); + } + else if (it->second->GetVariant() == InputDevice::DV_RAW) + { + InputDeviceKeyboardImplWinRaw* keyboardImpl = static_cast(keyboard->GetPimpl()); + GAINPUT_ASSERT(keyboardImpl); + keyboardImpl->HandleMessage(msg); + } + } + else if (it->second->GetType() == InputDevice::DT_MOUSE) + { + InputDeviceMouse* mouse = static_cast(it->second); + if (it->second->GetVariant() == InputDevice::DV_STANDARD) + { + InputDeviceMouseImplWin* mouseImpl = static_cast(mouse->GetPimpl()); + GAINPUT_ASSERT(mouseImpl); + mouseImpl->HandleMessage(msg); + } + else if (it->second->GetVariant() == InputDevice::DV_RAW) + { + InputDeviceMouseImplWinRaw* mouseImpl = static_cast(mouse->GetPimpl()); + GAINPUT_ASSERT(mouseImpl); + mouseImpl->HandleMessage(msg); + } + } + } +} +#endif + +#if defined(GAINPUT_PLATFORM_ANDROID) +int32_t +InputManager::HandleInput(AInputEvent* event) +{ + int handled = 0; + for (DeviceMap::const_iterator it = devices_.begin(); + it != devices_.end(); + ++it) + { +#if defined(GAINPUT_DEV) + if (it->second->IsSynced()) + { + continue; + } +#endif + if (it->second->GetType() == InputDevice::DT_TOUCH) + { + InputDeviceTouch* touch = static_cast(it->second); + InputDeviceTouchImplAndroid* touchImpl = static_cast(touch->GetPimpl()); + GAINPUT_ASSERT(touchImpl); + handled |= touchImpl->HandleInput(event); + } + else if (it->second->GetType() == InputDevice::DT_KEYBOARD) + { + InputDeviceKeyboard* keyboard = static_cast(it->second); + InputDeviceKeyboardImplAndroid* keyboardImpl = static_cast(keyboard->GetPimpl()); + GAINPUT_ASSERT(keyboardImpl); + handled |= keyboardImpl->HandleInput(event); + } + } + return handled; +} + +void +InputManager::HandleDeviceInput(DeviceInput const& input) +{ + DeviceId devId = FindDeviceId(input.deviceType, input.deviceIndex); + if (devId == InvalidDeviceId) + { + return; + } + + InputDevice* device = GetDevice(devId); + if (!device) + { + return; + } + +#if defined(GAINPUT_DEV) + if (device->IsSynced()) + { + return; + } +#endif + + InputState* state = device->GetNextInputState(); + if (!state) + { + state = device->GetInputState(); + } + if (!state) + { + return; + } + + if (input.buttonType == BT_BOOL) + { + EnqueueConcurrentChange(*device, *state, deltaState_, input.buttonId, input.value.b); + } + else if (input.buttonType == BT_FLOAT) + { + EnqueueConcurrentChange(*device, *state, deltaState_, input.buttonId, input.value.f); + } + else if (input.buttonType == BT_COUNT && input.deviceType == InputDevice::DT_PAD) + { + InputDevicePad* pad = static_cast(device); + InputDevicePadImplAndroid* impl = static_cast(pad->GetPimpl()); + GAINPUT_ASSERT(impl); + if (input.value.b) + { + impl->SetState(InputDevice::DeviceState::DS_OK); + } + else + { + impl->SetState(InputDevice::DeviceState::DS_UNAVAILABLE); + } + } +} + +#endif + +void +InputManager::ConnectForStateSync(const char* ip, unsigned port) +{ + GAINPUT_UNUSED(ip); GAINPUT_UNUSED(port); + GAINPUT_DEV_CONNECT(this, ip, port); +} + +void +InputManager::StartDeviceStateSync(DeviceId deviceId) +{ + GAINPUT_ASSERT(GetDevice(deviceId)); + GAINPUT_ASSERT(GetDevice(deviceId)->GetType() != InputDevice::DT_GESTURE); + GAINPUT_DEV_START_SYNC(deviceId); +} + +void +InputManager::SetDebugRenderingEnabled(bool enabled) +{ + debugRenderingEnabled_ = enabled; + if (enabled) + { + GAINPUT_ASSERT(debugRenderer_); + } +} + +void +InputManager::SetDebugRenderer(DebugRenderer* debugRenderer) +{ + debugRenderer_ = debugRenderer; +} + +void +InputManager::EnqueueConcurrentChange(InputDevice& device, InputState& state, InputDeltaState* delta, DeviceButtonId buttonId, bool value) +{ + Change change; + change.device = &device; + change.state = &state; + change.delta = delta; + change.buttonId = buttonId; + change.type = BT_BOOL; + change.b = value; + GAINPUT_CONC_ENQUEUE(concurrentInputs_, change); +} + +void +InputManager::EnqueueConcurrentChange(InputDevice& device, InputState& state, InputDeltaState* delta, DeviceButtonId buttonId, float value) +{ + Change change; + change.device = &device; + change.state = &state; + change.delta = delta; + change.buttonId = buttonId; + change.type = BT_FLOAT; + change.f = value; + GAINPUT_CONC_ENQUEUE(concurrentInputs_, change); +} + +} + +#if defined(GAINPUT_PLATFORM_ANDROID) +extern "C" { +JNIEXPORT void JNICALL +Java_de_johanneskuhlmann_gainput_Gainput_nativeOnInputBool(JNIEnv * /*env*/, jobject /*thiz*/, + jint deviceType, jint deviceIndex, + jint buttonId, jboolean value) +{ + if (!gGainputInputManager) + { + return; + } + using namespace gainput; + InputManager::DeviceInput input; + input.deviceType = static_cast(deviceType); + input.deviceIndex = deviceIndex; + input.buttonType = BT_BOOL; + if (input.deviceType == InputDevice::DT_KEYBOARD) + { + DeviceId deviceId = gGainputInputManager->FindDeviceId(input.deviceType, deviceIndex); + if (deviceId != InvalidDeviceId) + { + InputDevice* device = gGainputInputManager->GetDevice(deviceId); + if (device) + { + InputDeviceKeyboard* keyboard = static_cast(device); + InputDeviceKeyboardImplAndroid* keyboardImpl = static_cast(keyboard->GetPimpl()); + GAINPUT_ASSERT(keyboardImpl); + DeviceButtonId newId = keyboardImpl->Translate(buttonId); + if (newId != InvalidDeviceButtonId) + { + buttonId = newId; + } + } + } + } + input.buttonId = buttonId; + input.value.b = value; + gGainputInputManager->HandleDeviceInput(input); +} + +JNIEXPORT void JNICALL +Java_de_johanneskuhlmann_gainput_Gainput_nativeOnInputFloat(JNIEnv * /*env*/, jobject /*thiz*/, + jint deviceType, jint deviceIndex, + jint buttonId, jfloat value) +{ + if (!gGainputInputManager) + { + return; + } + using namespace gainput; + InputManager::DeviceInput input; + input.deviceType = static_cast(deviceType); + input.deviceIndex = deviceIndex; + input.buttonType = BT_FLOAT; + input.buttonId = buttonId; + input.value.f = value; + gGainputInputManager->HandleDeviceInput(input); +} + +JNIEXPORT void JNICALL +Java_de_johanneskuhlmann_gainput_Gainput_nativeOnDeviceChanged(JNIEnv * /*env*/, jobject /*thiz*/, + jint deviceId, jboolean value) +{ + if (!gGainputInputManager) + { + return; + } + using namespace gainput; + InputManager::DeviceInput input; + input.deviceType = InputDevice::DT_PAD; + input.deviceIndex = deviceId; + input.buttonType = BT_COUNT; + input.value.b = value; + gGainputInputManager->HandleDeviceInput(input); +} +} +#endif diff --git a/lib/source/gainput/GainputInputMap.cpp b/lib/source/gainput/GainputInputMap.cpp new file mode 100644 index 00000000..5df24180 --- /dev/null +++ b/lib/source/gainput/GainputInputMap.cpp @@ -0,0 +1,572 @@ + +#include +#include "dev/GainputDev.h" + +#include + +namespace +{ + template T Min(const T&a, const T& b) { return a < b ? a : b; } + template T Max(const T&a, const T& b) { return a < b ? b : a; } +} + +namespace gainput +{ + +class MappedInput +{ +public: + DeviceId device; + DeviceButtonId deviceButton; + + float rangeMin; + float rangeMax; + + FilterFunc_T filterFunc; + void* filterUserData; +}; + +typedef Array MappedInputList; + +class UserButton +{ +public: + + UserButtonId userButton; + MappedInputList inputs; + InputMap::UserButtonPolicy policy; + float deadZone; + + UserButton(Allocator& allocator) : + inputs(allocator), + deadZone(0.0f) + { } +}; + + +InputMap::InputMap(InputManager& manager, const char* name, Allocator& allocator) : + manager_(manager), + name_(0), + allocator_(allocator), + userButtons_(allocator_), + nextUserButtonId_(0), + listeners_(allocator_), + sortedListeners_(allocator_), + nextListenerId_(0), + managerListener_(0) +{ + static unsigned nextId = 0; + id_ = nextId++; + + if (name) + { + name_ = static_cast(allocator_.Allocate(strlen(name) + 1)); + strcpy(name_, name); + } + GAINPUT_DEV_NEW_MAP(this); +} + +InputMap::~InputMap() +{ + GAINPUT_DEV_REMOVE_MAP(this); + Clear(); + allocator_.Deallocate(name_); + + if (managerListener_) + { + manager_.RemoveListener(managerListenerId_); + allocator_.Delete(managerListener_); + } +} + +void +InputMap::Clear() +{ + for (UserButtonMap::iterator it = userButtons_.begin(); + it != userButtons_.end(); + ++it) + { + allocator_.Delete(it->second); + GAINPUT_DEV_REMOVE_USER_BUTTON(this, it->first); + } + userButtons_.clear(); + nextUserButtonId_ = 0; +} + +bool +InputMap::MapBool(UserButtonId userButton, DeviceId device, DeviceButtonId deviceButton) +{ + UserButton* ub = GetUserButton(userButton); + + if (!ub) + { + ub = allocator_.New(allocator_); + GAINPUT_ASSERT(ub); + ub->userButton = nextUserButtonId_++; + ub->policy = UBP_FIRST_DOWN; + userButtons_[userButton] = ub; + } + + MappedInput mi; + mi.device = device; + mi.deviceButton = deviceButton; + ub->inputs.push_back(mi); + + GAINPUT_DEV_NEW_USER_BUTTON(this, userButton, device, deviceButton); + + return true; +} + +bool +InputMap::MapFloat(UserButtonId userButton, DeviceId device, DeviceButtonId deviceButton, float min, float max, FilterFunc_T filterFunc, void* filterUserData) +{ + UserButton* ub = GetUserButton(userButton); + + if (!ub) + { + ub = allocator_.New(allocator_); + GAINPUT_ASSERT(ub); + ub->userButton = nextUserButtonId_++; + ub->policy = UBP_FIRST_DOWN; + userButtons_[userButton] = ub; + } + + MappedInput mi; + mi.device = device; + mi.deviceButton = deviceButton; + mi.rangeMin = min; + mi.rangeMax = max; + mi.filterFunc = filterFunc; + mi.filterUserData = filterUserData; + ub->inputs.push_back(mi); + + GAINPUT_DEV_NEW_USER_BUTTON(this, userButton, device, deviceButton); + + return true; +} + +void +InputMap::Unmap(UserButtonId userButton) +{ + UserButton* ub = GetUserButton(userButton); + if (ub) + { + allocator_.Delete(ub); + userButtons_.erase(userButton); + GAINPUT_DEV_REMOVE_USER_BUTTON(this, userButton); + } +} + +bool +InputMap::IsMapped(UserButtonId userButton) const +{ + return GetUserButton(userButton) != 0; +} + +size_t +InputMap::GetMappings(UserButtonId userButton, DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + size_t buttonCount = 0; + const UserButton* ub = GetUserButton(userButton); + if (!ub) + { + return 0; + } + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end() && buttonCount < maxButtonCount; + ++it, ++buttonCount) + { + const MappedInput& mi = *it; + outButtons[buttonCount].deviceId = mi.device; + outButtons[buttonCount].buttonId = mi.deviceButton; + } + return buttonCount; +} + +bool +InputMap::SetUserButtonPolicy(UserButtonId userButton, UserButtonPolicy policy) +{ + UserButton* ub = GetUserButton(userButton); + if (!ub) + { + return false; + } + ub->policy = policy; + return true; +} + +bool +InputMap::SetDeadZone(UserButtonId userButton, float deadZone) +{ + UserButton* ub = GetUserButton(userButton); + if (!ub) + { + return false; + } + ub->deadZone = deadZone; + return true; +} + +bool +InputMap::GetBool(UserButtonId userButton) const +{ + const UserButton* ub = GetUserButton(userButton); + GAINPUT_ASSERT(ub); + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end(); + ++it) + { + const MappedInput& mi = *it; + const InputDevice* device = manager_.GetDevice(mi.device); + GAINPUT_ASSERT(device); + if (device->GetBool(mi.deviceButton)) + { + return true; + } + } + return false; +} + +bool +InputMap::GetBoolIsNew(UserButtonId userButton) const +{ + const UserButton* ub = GetUserButton(userButton); + GAINPUT_ASSERT(ub); + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end(); + ++it) + { + const MappedInput& mi= *it; + const InputDevice* device = manager_.GetDevice(mi.device); + GAINPUT_ASSERT(device); + if (device->GetBool(mi.deviceButton) + && !device->GetBoolPrevious(mi.deviceButton)) + { + return true; + } + } + return false; +} + +bool +InputMap::GetBoolPrevious(UserButtonId userButton) const +{ + const UserButton* ub = GetUserButton(userButton); + GAINPUT_ASSERT(ub); + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end(); + ++it) + { + const MappedInput& mi= *it; + const InputDevice* device = manager_.GetDevice(mi.device); + GAINPUT_ASSERT(device); + if (device->GetBoolPrevious(mi.deviceButton)) + { + return true; + } + } + return false; +} + +bool +InputMap::GetBoolWasDown(UserButtonId userButton) const +{ + const UserButton* ub = GetUserButton(userButton); + GAINPUT_ASSERT(ub); + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end(); + ++it) + { + const MappedInput& mi= *it; + const InputDevice* device = manager_.GetDevice(mi.device); + GAINPUT_ASSERT(device); + if (!device->GetBool(mi.deviceButton) + && device->GetBoolPrevious(mi.deviceButton)) + { + return true; + } + } + return false; +} + +float +InputMap::GetFloat(UserButtonId userButton) const +{ + return GetFloatState(userButton, false); +} + +float +InputMap::GetFloatPrevious(UserButtonId userButton) const +{ + return GetFloatState(userButton, true); +} + +float +InputMap::GetFloatDelta(UserButtonId userButton) const +{ + return GetFloat(userButton) - GetFloatPrevious(userButton); +} + +float +InputMap::GetFloatState(UserButtonId userButton, bool previous) const +{ + float value = 0.0f; + int downCount = 0; + const UserButton* ub = GetUserButton(userButton); + GAINPUT_ASSERT(ub); + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end(); + ++it) + { + const MappedInput& mi= *it; + const InputDevice* device = manager_.GetDevice(mi.device); + GAINPUT_ASSERT(device); + + bool down = false; + float deviceValue = 0.0f; + if (device->GetButtonType(mi.deviceButton) == BT_BOOL) + { + down = previous ? device->GetBoolPrevious(mi.deviceButton) : device->GetBool(mi.deviceButton); + deviceValue = down ? mi.rangeMax : mi.rangeMin; + } + else + { + const float tmpValue = previous ? device->GetFloatPrevious(mi.deviceButton) : device->GetFloat(mi.deviceButton); + if (tmpValue != 0.0f) + { + GAINPUT_ASSERT(device->GetButtonType(mi.deviceButton) == BT_FLOAT); + deviceValue = mi.rangeMin + tmpValue*mi.rangeMax; + down = true; + } + } + + if (mi.filterFunc) + { + deviceValue = mi.filterFunc(deviceValue, mi.filterUserData); + } + + if (down) + { + ++downCount; + if (ub->policy == UBP_FIRST_DOWN) + { + value = deviceValue; + break; + } + else if (ub->policy == UBP_MAX) + { + if (Abs(deviceValue) > Abs(value)) + { + value = deviceValue; + } + } + else if (ub->policy == UBP_MIN) + { + if (downCount == 1) + { + value = deviceValue; + } + else + { + if (Abs(deviceValue) < Abs(value)) + { + value = deviceValue; + } + } + } + else if (ub->policy == UBP_AVERAGE) + { + value += deviceValue; + } + } + } + + if (ub->policy == UBP_AVERAGE && downCount) + { + value /= float(downCount); + } + + if (Abs(value) <= ub->deadZone) + { + value = 0.0f; + } + + return value; +} + +size_t +InputMap::GetUserButtonName(UserButtonId userButton, char* buffer, size_t bufferLength) const +{ + const UserButton* ub = GetUserButton(userButton); + GAINPUT_ASSERT(ub); + for (MappedInputList::const_iterator it = ub->inputs.begin(); + it != ub->inputs.end(); + ++it) + { + const MappedInput& mi = *it; + const InputDevice* device = manager_.GetDevice(mi.device); + GAINPUT_ASSERT(device); + return device->GetButtonName(mi.deviceButton, buffer, bufferLength); + } + return 0; +} + +UserButtonId +InputMap::GetUserButtonId(DeviceId device, DeviceButtonId deviceButton) const +{ + for (UserButtonMap::const_iterator it = userButtons_.begin(); + it != userButtons_.end(); + ++it) + { + const UserButton* ub = it->second; + for (MappedInputList::const_iterator it2 = ub->inputs.begin(); + it2 != ub->inputs.end(); + ++it2) + { + const MappedInput& mi = *it2; + if (mi.device == device && mi.deviceButton == deviceButton) + { + return it->first; + } + } + } + return InvalidUserButtonId; +} + +namespace { + +class ManagerToMapListener : public InputListener +{ +public: + ManagerToMapListener(InputMap& inputMap, Array& listeners) : + inputMap_(inputMap), + listeners_(listeners) + { } + + bool OnDeviceButtonBool(DeviceId device, DeviceButtonId deviceButton, bool oldValue, bool newValue) + { + const UserButtonId userButton = inputMap_.GetUserButtonId(device, deviceButton); + if (userButton == InvalidUserButtonId) + { + return true; + } + for (Array::iterator it = listeners_.begin(); + it != listeners_.end(); + ++it) + { + if(!(*it)->OnUserButtonBool(userButton, oldValue, newValue)) + { + break; + } + } + return true; + } + + bool OnDeviceButtonFloat(DeviceId device, DeviceButtonId deviceButton, float oldValue, float newValue) + { + const UserButtonId userButton = inputMap_.GetUserButtonId(device, deviceButton); + if (userButton == InvalidUserButtonId) + { + return true; + } + for (Array::iterator it = listeners_.begin(); + it != listeners_.end(); + ++it) + { + if (!(*it)->OnUserButtonFloat(userButton, oldValue, newValue)) + { + break; + } + } + return true; + } + +private: + InputMap& inputMap_; + Array& listeners_; +}; + +} + +unsigned +InputMap::AddListener(MappedInputListener* listener) +{ + if (!managerListener_) + { + managerListener_ = allocator_.New(*this, sortedListeners_); + managerListenerId_ = manager_.AddListener(managerListener_); + } + listeners_[nextListenerId_] = listener; + ReorderListeners(); + return nextListenerId_++; +} + +void +InputMap::RemoveListener(unsigned listenerId) +{ + listeners_.erase(listenerId); + ReorderListeners(); + + if (listeners_.empty()) + { + manager_.RemoveListener(managerListenerId_); + allocator_.Delete(managerListener_); + managerListener_ = 0; + } +} + +namespace { +static int CompareListeners(const void* a, const void* b) +{ + const MappedInputListener* listener1 = *reinterpret_cast(a); + const MappedInputListener* listener2 = *reinterpret_cast(b); + return listener2->GetPriority() - listener1->GetPriority(); +} +} + +void +InputMap::ReorderListeners() +{ + sortedListeners_.clear(); + for (HashMap::iterator it = listeners_.begin(); + it != listeners_.end(); + ++it) + { + sortedListeners_.push_back(it->second); + } + + if (sortedListeners_.empty()) + { + return; + } + + qsort(&sortedListeners_[0], + sortedListeners_.size(), + sizeof(MappedInputListener*), + &CompareListeners); +} + +UserButton* +InputMap::GetUserButton(UserButtonId userButton) +{ + UserButtonMap::iterator it = userButtons_.find(userButton); + if (it == userButtons_.end()) + { + return 0; + } + return it->second; +} + +const UserButton* +InputMap::GetUserButton(UserButtonId userButton) const +{ + UserButtonMap::const_iterator it = userButtons_.find(userButton); + if (it == userButtons_.end()) + { + return 0; + } + return it->second; +} + +} + diff --git a/lib/source/gainput/GainputInputState.cpp b/lib/source/gainput/GainputInputState.cpp new file mode 100644 index 00000000..1c212611 --- /dev/null +++ b/lib/source/gainput/GainputInputState.cpp @@ -0,0 +1,32 @@ + +#include + + +namespace gainput +{ + +InputState::InputState(Allocator& allocator, unsigned int buttonCount) : + allocator_(allocator), + buttonCount_(buttonCount) +{ + const size_t size = sizeof(Button) * buttonCount_; + buttons_ = static_cast(allocator_.Allocate(size)); + GAINPUT_ASSERT(buttons_); + memset(buttons_, 0, size); +} + +InputState::~InputState() +{ + allocator_.Deallocate(buttons_); +} + +InputState& +InputState::operator=(const InputState& other) +{ + const size_t size = sizeof(Button) * buttonCount_; + memcpy(buttons_, other.buttons_, size); + return *this; +} + +} + diff --git a/lib/source/gainput/GainputIos.mm b/lib/source/gainput/GainputIos.mm new file mode 100644 index 00000000..ace40303 --- /dev/null +++ b/lib/source/gainput/GainputIos.mm @@ -0,0 +1,185 @@ + +#include + +#if defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS) + +#include + +#include "touch/GainputInputDeviceTouchIos.h" + +@implementation GainputView +{ + gainput::InputManager* inputManager_; +} + +- (id)initWithFrame:(CGRect)frame inputManager:(gainput::InputManager&)inputManager +{ + self = [super initWithFrame:frame]; + if (self) + { + inputManager_ = &inputManager; + +#if !defined(GAINPUT_PLATFORM_TVOS) + [self setMultipleTouchEnabled:YES]; +#endif + } + + gainput::DeviceId deviceId = inputManager_->FindDeviceId(gainput::InputDevice::DT_TOUCH, 0); + if (deviceId != gainput::InvalidDeviceId) + { + gainput::InputDeviceTouch* device = static_cast(inputManager_->GetDevice(deviceId)); + if (device) + { + gainput::InputDeviceTouchImplIos* deviceImpl = static_cast(device->GetPimpl()); + if (deviceImpl) + { + bool supports = false; + UIWindow* window = [UIApplication sharedApplication].keyWindow; + if (window) + { + UIViewController* rvc = [window rootViewController]; + if (rvc) + { + UITraitCollection* tc = [rvc traitCollection]; + if (tc && [tc respondsToSelector:@selector(forceTouchCapability)]) + { + supports = [tc forceTouchCapability] == UIForceTouchCapabilityAvailable; + } + } + } + deviceImpl->SetSupportsPressure(supports); + } + } + } + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + gainput::DeviceId deviceId = inputManager_->FindDeviceId(gainput::InputDevice::DT_TOUCH, 0); + if (deviceId == gainput::InvalidDeviceId) + { + return; + } + gainput::InputDeviceTouch* device = static_cast(inputManager_->GetDevice(deviceId)); + if (!device) + { + return; + } + gainput::InputDeviceTouchImplIos* deviceImpl = static_cast(device->GetPimpl()); + if (!deviceImpl) + { + return; + } + + for (UITouch *touch in touches) + { + CGPoint point = [touch locationInView:self]; + + CGFloat force = 0.f; + CGFloat maxForce = 1.f; + if ([touch respondsToSelector:@selector(force)]) + { + maxForce = [touch maximumPossibleForce]; + force = [touch force]; + } + + float x = point.x / self.bounds.size.width; + float y = point.y / self.bounds.size.height; + float z = force / maxForce; + + deviceImpl->HandleTouch(static_cast(touch), x, y, z); + } +} + + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + gainput::DeviceId deviceId = inputManager_->FindDeviceId(gainput::InputDevice::DT_TOUCH, 0); + if (deviceId == gainput::InvalidDeviceId) + { + return; + } + gainput::InputDeviceTouch* device = static_cast(inputManager_->GetDevice(deviceId)); + if (!device) + { + return; + } + gainput::InputDeviceTouchImplIos* deviceImpl = static_cast(device->GetPimpl()); + if (!deviceImpl) + { + return; + } + + for (UITouch *touch in touches) + { + CGPoint point = [touch locationInView:self]; + + CGFloat force = 0.f; + CGFloat maxForce = 1.f; + if ([touch respondsToSelector:@selector(force)]) + { + maxForce = [touch maximumPossibleForce]; + force = [touch force]; + } + + float x = point.x / self.bounds.size.width; + float y = point.y / self.bounds.size.height; + float z = force / maxForce; + + deviceImpl->HandleTouch(static_cast(touch), x, y, z); + } +} + + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + gainput::DeviceId deviceId = inputManager_->FindDeviceId(gainput::InputDevice::DT_TOUCH, 0); + if (deviceId == gainput::InvalidDeviceId) + { + return; + } + gainput::InputDeviceTouch* device = static_cast(inputManager_->GetDevice(deviceId)); + if (!device) + { + return; + } + gainput::InputDeviceTouchImplIos* deviceImpl = static_cast(device->GetPimpl()); + if (!deviceImpl) + { + return; + } + + for (UITouch *touch in touches) + { + CGPoint point = [touch locationInView:self]; + + CGFloat force = 0.f; + CGFloat maxForce = 1.f; + if ([touch respondsToSelector:@selector(force)]) + { + maxForce = [touch maximumPossibleForce]; + force = [touch force]; + } + + float x = point.x / self.bounds.size.width; + float y = point.y / self.bounds.size.height; + float z = force / maxForce; + + deviceImpl->HandleTouchEnd(static_cast(touch), x, y, z); + } +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event +{ + [self touchesEnded:touches withEvent:event]; +} + +@end + +#endif diff --git a/lib/source/gainput/GainputMac.mm b/lib/source/gainput/GainputMac.mm new file mode 100644 index 00000000..82e8a605 --- /dev/null +++ b/lib/source/gainput/GainputMac.mm @@ -0,0 +1,18 @@ + +#include + +#ifdef GAINPUT_PLATFORM_MAC + +#import + +namespace gainput +{ + +bool MacIsApplicationKey() +{ + return [[NSApplication sharedApplication] keyWindow ] != nil; +} + +} + +#endif diff --git a/lib/source/gainput/GainputMapFilters.cpp b/lib/source/gainput/GainputMapFilters.cpp new file mode 100644 index 00000000..866a42bb --- /dev/null +++ b/lib/source/gainput/GainputMapFilters.cpp @@ -0,0 +1,20 @@ + +#include +#include + + +namespace gainput +{ + +float InvertSymmetricInput(float const value, void*) +{ + return -value; +} + +float InvertInput(float const value, void*) +{ + return 1.0f - value; +} + +} + diff --git a/lib/source/gainput/GainputWindows.h b/lib/source/gainput/GainputWindows.h new file mode 100644 index 00000000..53ad353e --- /dev/null +++ b/lib/source/gainput/GainputWindows.h @@ -0,0 +1,17 @@ + +#ifndef GAINPUTWINDOWS_H_ +#define GAINPUTWINDOWS_H_ + +#define WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#ifdef DrawText +#undef DrawText +#endif + +#endif diff --git a/lib/source/gainput/builtin/GainputInputDeviceBuiltIn.cpp b/lib/source/gainput/builtin/GainputInputDeviceBuiltIn.cpp new file mode 100644 index 00000000..fbd0eac4 --- /dev/null +++ b/lib/source/gainput/builtin/GainputInputDeviceBuiltIn.cpp @@ -0,0 +1,145 @@ + +#include +#include + +#include "GainputInputDeviceBuiltInImpl.h" +#include +#include +#include + +#if defined(GAINPUT_PLATFORM_ANDROID) + #include "GainputInputDeviceBuiltInAndroid.h" +#elif defined(GAINPUT_PLATFORM_IOS) + #include "GainputInputDeviceBuiltInIos.h" +#endif + +#include "GainputInputDeviceBuiltInNull.h" + +namespace gainput +{ + +namespace +{ +struct DeviceButtonInfo +{ + ButtonType type; + const char* name; +}; + +DeviceButtonInfo deviceButtonInfos[] = +{ + { BT_FLOAT, "builtin_acceleration_x" }, + { BT_FLOAT, "builtin_acceleration_y" }, + { BT_FLOAT, "builtin_acceleration_z" }, + { BT_FLOAT, "builtin_gravity_x" }, + { BT_FLOAT, "builtin_gravity_y" }, + { BT_FLOAT, "builtin_gravity_z" }, + { BT_FLOAT, "builtin_gyroscope_x" }, + { BT_FLOAT, "builtin_gyroscope_y" }, + { BT_FLOAT, "builtin_gyroscope_z" }, + { BT_FLOAT, "builtin_magneticfield_x" }, + { BT_FLOAT, "builtin_magneticfield_y" }, + { BT_FLOAT, "builtin_magneticfield_z" }, +}; + +const unsigned BuiltInButtonCount = BuiltInButtonCount_; + +} + + +InputDeviceBuiltIn::InputDeviceBuiltIn(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_BUILTIN) : 0), + impl_(0) +{ + state_ = manager.GetAllocator().New(manager.GetAllocator(), BuiltInButtonCount); + GAINPUT_ASSERT(state_); + previousState_ = manager.GetAllocator().New(manager.GetAllocator(), BuiltInButtonCount); + GAINPUT_ASSERT(previousState_); + +#if defined(GAINPUT_PLATFORM_ANDROID) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#elif defined(GAINPUT_PLATFORM_IOS) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#endif + + if (!impl_) + { + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); + } + + GAINPUT_ASSERT(impl_); +} + +InputDeviceBuiltIn::~InputDeviceBuiltIn() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); + manager_.GetAllocator().Delete(impl_); +} + +void +InputDeviceBuiltIn::InternalUpdate(InputDeltaState* delta) +{ + impl_->Update(delta); +} + +InputDevice::DeviceState +InputDeviceBuiltIn::InternalGetState() const +{ + return impl_->GetState(); +} + +InputDevice::DeviceVariant +InputDeviceBuiltIn::GetVariant() const +{ + return impl_->GetVariant(); +} + +bool +InputDeviceBuiltIn::IsValidButtonId(DeviceButtonId deviceButton) const +{ + return impl_->IsValidButton(deviceButton); +} + +size_t +InputDeviceBuiltIn::GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + GAINPUT_ASSERT(outButtons); + GAINPUT_ASSERT(maxButtonCount > 0); + return CheckAllButtonsDown(outButtons, maxButtonCount, BuiltInButtonAccelerationX, BuiltInButtonCount_); +} + +size_t +InputDeviceBuiltIn::GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + GAINPUT_ASSERT(buffer); + GAINPUT_ASSERT(bufferLength > 0); + strncpy(buffer, deviceButtonInfos[deviceButton].name, bufferLength); + buffer[bufferLength-1] = 0; + const size_t nameLen = strlen(deviceButtonInfos[deviceButton].name); + return nameLen >= bufferLength ? bufferLength : nameLen+1; +} + +ButtonType +InputDeviceBuiltIn::GetButtonType(DeviceButtonId deviceButton) const +{ + return deviceButtonInfos[deviceButton].type; +} + +DeviceButtonId +InputDeviceBuiltIn::GetButtonByName(const char* name) const +{ + GAINPUT_ASSERT(name); + for (unsigned i = 0; i < BuiltInButtonCount; ++i) + { + if (strcmp(name, deviceButtonInfos[i].name) == 0) + { + return DeviceButtonId(i); + } + } + return InvalidDeviceButtonId; +} + +} + diff --git a/lib/source/gainput/builtin/GainputInputDeviceBuiltInAndroid.h b/lib/source/gainput/builtin/GainputInputDeviceBuiltInAndroid.h new file mode 100644 index 00000000..11d25323 --- /dev/null +++ b/lib/source/gainput/builtin/GainputInputDeviceBuiltInAndroid.h @@ -0,0 +1,203 @@ + +#ifndef GAINPUTINPUTDEVICEBUILTINANDROID_H_ +#define GAINPUTINPUTDEVICEBUILTINANDROID_H_ + +#include + +namespace gainput +{ + +class InputDeviceBuiltInImplAndroid : public InputDeviceBuiltInImpl +{ +public: + InputDeviceBuiltInImplAndroid(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(state), + previousState_(previousState), + deviceState_(InputDevice::DS_UNAVAILABLE), + buttonDialect_(manager_.GetAllocator()), + sensorManager_(0), + accelerometerSensor_(0), + gyroscopeSensor_(0), + sensorEventQueue_(0), + gravityInitialized_(false) + { + ALooper* looper = ALooper_forThread(); + if (!looper) + { + looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + } + + if (!looper) + { + return; + } + + sensorManager_ = ASensorManager_getInstance(); + if (!sensorManager_) + { + return; + } + + sensorEventQueue_ = ASensorManager_createEventQueue(sensorManager_, looper, ALOOPER_POLL_CALLBACK, NULL, NULL); + if (!sensorEventQueue_) + { + return; + } + + accelerometerSensor_ = ASensorManager_getDefaultSensor(sensorManager_, ASENSOR_TYPE_ACCELEROMETER); + if (accelerometerSensor_) + { + ASensorEventQueue_setEventRate(sensorEventQueue_, accelerometerSensor_, ASensor_getMinDelay(accelerometerSensor_)); + ASensorEventQueue_enableSensor(sensorEventQueue_, accelerometerSensor_); + } + + gyroscopeSensor_ = ASensorManager_getDefaultSensor(sensorManager_, ASENSOR_TYPE_GYROSCOPE); + if (gyroscopeSensor_) + { + ASensorEventQueue_setEventRate(sensorEventQueue_, gyroscopeSensor_, ASensor_getMinDelay(gyroscopeSensor_)); + ASensorEventQueue_enableSensor(sensorEventQueue_, gyroscopeSensor_); + } + + magneticFieldSensor_ = ASensorManager_getDefaultSensor(sensorManager_, ASENSOR_TYPE_MAGNETIC_FIELD); + if (magneticFieldSensor_) + { + ASensorEventQueue_setEventRate(sensorEventQueue_, magneticFieldSensor_, ASensor_getMinDelay(magneticFieldSensor_)); + ASensorEventQueue_enableSensor(sensorEventQueue_, magneticFieldSensor_); + } + + deviceState_ = InputDevice::DS_OK; + } + + ~InputDeviceBuiltInImplAndroid() + { + if (!sensorManager_) + { + return; + } + + if (!sensorEventQueue_) + { + return; + } + + + if (accelerometerSensor_) + { + ASensorEventQueue_disableSensor(sensorEventQueue_, accelerometerSensor_); + } + + if (gyroscopeSensor_) + { + ASensorEventQueue_disableSensor(sensorEventQueue_, gyroscopeSensor_); + } + + if (magneticFieldSensor_) + { + ASensorEventQueue_disableSensor(sensorEventQueue_, magneticFieldSensor_); + } + + ASensorManager_destroyEventQueue(sensorManager_, sensorEventQueue_); + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + ASensorEvent event; + + while (ASensorEventQueue_getEvents(sensorEventQueue_, &event, 1) > 0) + { + if (event.type == ASENSOR_TYPE_ACCELEROMETER) + { + // https://developer.android.com/reference/android/hardware/SensorEvent.html#values + static const float kAlpha = 0.8f; + + if (!gravityInitialized_) + { + gravity_[0] = event.acceleration.x / ASENSOR_STANDARD_GRAVITY; + gravity_[1] = event.acceleration.y / ASENSOR_STANDARD_GRAVITY; + gravity_[2] = event.acceleration.z / ASENSOR_STANDARD_GRAVITY; + gravityInitialized_ = true; + } + + gravity_[0] = kAlpha * gravity_[0] + (1.0f - kAlpha) * (event.acceleration.x / ASENSOR_STANDARD_GRAVITY); + gravity_[1] = kAlpha * gravity_[1] + (1.0f - kAlpha) * (event.acceleration.y / ASENSOR_STANDARD_GRAVITY); + gravity_[2] = kAlpha * gravity_[2] + (1.0f - kAlpha) * (event.acceleration.z / ASENSOR_STANDARD_GRAVITY); + + float accel[3]; + accel[0] = (event.acceleration.x / ASENSOR_STANDARD_GRAVITY) - gravity_[0]; + accel[1] = (event.acceleration.y / ASENSOR_STANDARD_GRAVITY) - gravity_[1]; + accel[2] = (event.acceleration.z / ASENSOR_STANDARD_GRAVITY) - gravity_[2]; + + HandleAxis(device_, state_, delta, BuiltInButtonGravityX, gravity_[0]); + HandleAxis(device_, state_, delta, BuiltInButtonGravityY, gravity_[1]); + HandleAxis(device_, state_, delta, BuiltInButtonGravityZ, gravity_[2]); + HandleAxis(device_, state_, delta, BuiltInButtonAccelerationX, accel[0]); + HandleAxis(device_, state_, delta, BuiltInButtonAccelerationY, accel[1]); + HandleAxis(device_, state_, delta, BuiltInButtonAccelerationZ, accel[2]); + } + else if (event.type == ASENSOR_TYPE_GYROSCOPE) + { + HandleAxis(device_, state_, delta, BuiltInButtonGyroscopeX, event.vector.x / ASENSOR_STANDARD_GRAVITY); + HandleAxis(device_, state_, delta, BuiltInButtonGyroscopeY, event.vector.y / ASENSOR_STANDARD_GRAVITY); + HandleAxis(device_, state_, delta, BuiltInButtonGyroscopeZ, event.vector.z / ASENSOR_STANDARD_GRAVITY); + } + else if (event.type == ASENSOR_TYPE_MAGNETIC_FIELD) + { + HandleAxis(device_, state_, delta, BuiltInButtonMagneticFieldX, event.magnetic.x / ASENSOR_MAGNETIC_FIELD_EARTH_MAX); + HandleAxis(device_, state_, delta, BuiltInButtonMagneticFieldY, event.magnetic.y / ASENSOR_MAGNETIC_FIELD_EARTH_MAX); + HandleAxis(device_, state_, delta, BuiltInButtonMagneticFieldZ, event.magnetic.z / ASENSOR_MAGNETIC_FIELD_EARTH_MAX); + } + } + } + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + bool IsValidButton(DeviceButtonId deviceButton) const + { + if (deviceButton <= BuiltInButtonAccelerationZ && accelerometerSensor_) + { + return true; + } + + if (deviceButton >= BuiltInButtonGyroscopeX && deviceButton <= BuiltInButtonGyroscopeZ && gyroscopeSensor_) + { + return true; + } + + if (deviceButton >= BuiltInButtonMagneticFieldX && deviceButton <= BuiltInButtonMagneticFieldZ && magneticFieldSensor_) + { + return true; + } + + return false; + } + +private: + InputManager& manager_; + InputDevice& device_; + InputState& state_; + InputState& previousState_; + InputDevice::DeviceState deviceState_; + HashMap buttonDialect_; + ASensorManager* sensorManager_; + const ASensor* accelerometerSensor_; + const ASensor* gyroscopeSensor_; + const ASensor* magneticFieldSensor_; + ASensorEventQueue* sensorEventQueue_; + float gravity_[3]; + bool gravityInitialized_; +}; + +} + +#endif + diff --git a/lib/source/gainput/builtin/GainputInputDeviceBuiltInImpl.h b/lib/source/gainput/builtin/GainputInputDeviceBuiltInImpl.h new file mode 100644 index 00000000..ac8e21a7 --- /dev/null +++ b/lib/source/gainput/builtin/GainputInputDeviceBuiltInImpl.h @@ -0,0 +1,21 @@ + +#ifndef GAINPUTINPUTDEVICEBUILTINIMPL_H_ +#define GAINPUTINPUTDEVICEBUILTINIMPL_H_ + +namespace gainput +{ + +class InputDeviceBuiltInImpl +{ +public: + virtual ~InputDeviceBuiltInImpl() { } + virtual InputDevice::DeviceVariant GetVariant() const = 0; + virtual InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } + virtual void Update(InputDeltaState* delta) = 0; + virtual bool IsValidButton(DeviceButtonId deviceButton) const = 0; +}; + +} + +#endif + diff --git a/lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.h b/lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.h new file mode 100644 index 00000000..4a6c562a --- /dev/null +++ b/lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.h @@ -0,0 +1,52 @@ + +#ifndef GAINPUTINPUTDEVICEBUILTINIOS_H_ +#define GAINPUTINPUTDEVICEBUILTINIOS_H_ + + +namespace gainput +{ + +class InputDeviceBuiltInImplIos : public InputDeviceBuiltInImpl +{ +public: + InputDeviceBuiltInImplIos(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState); + ~InputDeviceBuiltInImplIos(); + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta); + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + bool IsValidButton(DeviceButtonId deviceButton) const; + + bool Vibrate(float leftMotor, float rightMotor) + { + return false; + } + + bool pausePressed_; + +private: + InputManager& manager_; + InputDevice& device_; + unsigned index_; + bool padFound_; + InputState& state_; + InputState& previousState_; + InputDevice::DeviceState deviceState_; + + void* motionManager_; + +}; + +} + +#endif + diff --git a/lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.mm b/lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.mm new file mode 100644 index 00000000..b6838696 --- /dev/null +++ b/lib/source/gainput/builtin/GainputInputDeviceBuiltInIos.mm @@ -0,0 +1,82 @@ +#include + +#ifdef GAINPUT_PLATFORM_IOS + +#include "GainputInputDeviceBuiltInImpl.h" +#include +#include +#include + +#include "GainputInputDeviceBuiltInIos.h" + +#import + +namespace gainput +{ + +InputDeviceBuiltInImplIos::InputDeviceBuiltInImplIos(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) + : pausePressed_(false), + manager_(manager), + device_(device), + index_(index), + padFound_(false), + state_(state), + previousState_(previousState), + deviceState_(InputDevice::DS_UNAVAILABLE), + motionManager_(0) +{ + CMMotionManager* motionManager = [[CMMotionManager alloc] init]; + [motionManager startDeviceMotionUpdates]; + motionManager_ = motionManager; + deviceState_ = InputDevice::DS_OK; +} + +InputDeviceBuiltInImplIos::~InputDeviceBuiltInImplIos() +{ + CMMotionManager* motionManager = reinterpret_cast(motionManager_); + [motionManager stopDeviceMotionUpdates]; + [motionManager release]; +} + +void InputDeviceBuiltInImplIos::Update(InputDeltaState* delta) +{ + GAINPUT_ASSERT(motionManager_); + @autoreleasepool { + CMMotionManager* motionManager = reinterpret_cast(motionManager_); + CMDeviceMotion* motion = motionManager.deviceMotion; + if (!motion) + { + return; + } + + HandleAxis(device_, state_, delta, BuiltInButtonAccelerationX, motion.userAcceleration.x); + HandleAxis(device_, state_, delta, BuiltInButtonAccelerationY, motion.userAcceleration.y); + HandleAxis(device_, state_, delta, BuiltInButtonAccelerationZ, motion.userAcceleration.z); + + HandleAxis(device_, state_, delta, BuiltInButtonGravityX, motion.gravity.x); + HandleAxis(device_, state_, delta, BuiltInButtonGravityY, motion.gravity.y); + HandleAxis(device_, state_, delta, BuiltInButtonGravityZ, motion.gravity.z); + + const float gyroX = 2.0f * (motion.attitude.quaternion.x * motion.attitude.quaternion.z + motion.attitude.quaternion.w * motion.attitude.quaternion.y); + const float gyroY = 2.0f * (motion.attitude.quaternion.y * motion.attitude.quaternion.z - motion.attitude.quaternion.w * motion.attitude.quaternion.x); + const float gyroZ = 1.0f - 2.0f * (motion.attitude.quaternion.x * motion.attitude.quaternion.x + motion.attitude.quaternion.y * motion.attitude.quaternion.y); + HandleAxis(device_, state_, delta, BuiltInButtonGyroscopeX, gyroX); + HandleAxis(device_, state_, delta, BuiltInButtonGyroscopeY, gyroY); + HandleAxis(device_, state_, delta, BuiltInButtonGyroscopeZ, gyroZ); + + HandleAxis(device_, state_, delta, BuiltInButtonMagneticFieldX, motion.magneticField.field.x); + HandleAxis(device_, state_, delta, BuiltInButtonMagneticFieldY, motion.magneticField.field.y); + HandleAxis(device_, state_, delta, BuiltInButtonMagneticFieldZ, motion.magneticField.field.z); + + } +} + +bool InputDeviceBuiltInImplIos::IsValidButton(DeviceButtonId deviceButton) const +{ + return deviceButton >= BuiltInButtonAccelerationX && deviceButton <= BuiltInButtonMagneticFieldZ; +} + +} + +#endif + diff --git a/lib/source/gainput/builtin/GainputInputDeviceBuiltInNull.h b/lib/source/gainput/builtin/GainputInputDeviceBuiltInNull.h new file mode 100644 index 00000000..0f8a76e6 --- /dev/null +++ b/lib/source/gainput/builtin/GainputInputDeviceBuiltInNull.h @@ -0,0 +1,38 @@ + +#ifndef GAINPUTINPUTDEVICEBUILTINNULL_H_ +#define GAINPUTINPUTDEVICEBUILTINNULL_H_ + + +namespace gainput +{ + +class InputDeviceBuiltInImplNull : public InputDeviceBuiltInImpl +{ +public: + InputDeviceBuiltInImplNull(InputManager& /*manager*/, InputDevice& /*device*/, unsigned /*index*/, InputState& /*state*/, InputState& /*previousState*/) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_NULL; + } + + void Update(InputDeltaState* /*delta*/) + { + } + + InputDevice::DeviceState GetState() const + { + return InputDevice::DS_OK; + } + + bool IsValidButton(DeviceButtonId /*deviceButton*/) const + { + return false; + } +}; + +} + +#endif diff --git a/lib/source/gainput/dev/GainputDev.cpp b/lib/source/gainput/dev/GainputDev.cpp new file mode 100644 index 00000000..22507d85 --- /dev/null +++ b/lib/source/gainput/dev/GainputDev.cpp @@ -0,0 +1,717 @@ + +#include + +#ifdef GAINPUT_DEV + +#include + +#if defined(GAINPUT_PLATFORM_ANDROID) +#include +#endif + +#include "GainputDev.h" +#include "GainputNetAddress.h" +#include "GainputNetListener.h" +#include "GainputNetConnection.h" +#include "GainputMemoryStream.h" +#include +#include +#include + +#if _MSC_VER +#define snprintf _snprintf +#endif + + +namespace gainput +{ + +static NetListener* devListener = 0; +static NetConnection* devConnection = 0; +static InputManager* inputManager = 0; +static Allocator* allocator = 0; +static Array devMaps(GetDefaultAllocator()); +static Array devDevices(GetDefaultAllocator()); +static bool devSendInfos = false; +static Array devSyncedDevices(GetDefaultAllocator()); +static size_t devFrame = 0; +static bool useHttp = false; + +static const unsigned kMaxReadTries = 1000000; + +static size_t SendMessage(Stream& stream) +{ + const uint8_t length = stream.GetSize(); + GAINPUT_ASSERT(length > 0); + size_t sent = devConnection->Send(&length, sizeof(length)); + sent += devConnection->Send(stream); + return sent; +} + +static bool ReadMessage(Stream& stream, bool& outFailed) +{ + stream.Reset(); + size_t received = devConnection->Receive(stream, 1); + if (!received) + return false; + uint8_t length; + stream.Read(length); + stream.Reset(); + received = 0; + unsigned tries = 0; + bool failed = false; + while (received < length) + { + stream.SeekEnd(0); + received += devConnection->Receive(stream, length - received); + ++tries; + if (tries >= kMaxReadTries) + { + failed = true; + break; + } + } + outFailed = failed; + if (failed) + { + GAINPUT_LOG("GAINPUT: ReadMessage failed.\n"); + return false; + } + stream.SeekBegin(0); + return true; +} + +class DevUserButtonListener : public MappedInputListener +{ +public: + DevUserButtonListener(const InputMap* map) : map_(map) { } + + bool OnUserButtonBool(UserButtonId userButton, bool, bool newValue) + { + if (!devConnection || !devSendInfos) + { + return true; + } + + Stream* stream = allocator->New(32, *allocator); + stream->Write(uint8_t(DevCmdUserButtonChanged)); + stream->Write(uint32_t(map_->GetId())); + stream->Write(uint32_t(userButton)); + stream->Write(uint8_t(0)); + stream->Write(uint8_t(newValue)); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); + + return true; + } + + bool OnUserButtonFloat(UserButtonId userButton, float, float newValue) + { + if (!devConnection || !devSendInfos) + { + return true; + } + + Stream* stream = allocator->New(32, *allocator); + stream->Write(uint8_t(DevCmdUserButtonChanged)); + stream->Write(uint32_t(map_->GetId())); + stream->Write(uint32_t(userButton)); + stream->Write(uint8_t(1)); + stream->Write(newValue); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); + + return true; + } + +private: + const InputMap* map_; +}; + +class DevDeviceButtonListener : public InputListener +{ +public: + DevDeviceButtonListener(const InputManager* inputManager) : inputManager_(inputManager) { } + + bool OnDeviceButtonBool(DeviceId deviceId, DeviceButtonId deviceButton, bool, bool newValue) + { + if (!devConnection || devSyncedDevices.find(deviceId) == devSyncedDevices.end()) + return true; + const InputDevice* device = inputManager_->GetDevice(deviceId); + GAINPUT_ASSERT(device); + Stream* stream = allocator->New(32, *allocator); + stream->Write(uint8_t(DevCmdSetDeviceButton)); + stream->Write(uint8_t(device->GetType())); + stream->Write(uint8_t(device->GetIndex())); + stream->Write(uint32_t(deviceButton)); + stream->Write(uint8_t(newValue)); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); + return true; + } + + bool OnDeviceButtonFloat(DeviceId deviceId, DeviceButtonId deviceButton, float, float newValue) + { + if (!devConnection || devSyncedDevices.find(deviceId) == devSyncedDevices.end()) + return true; + const InputDevice* device = inputManager_->GetDevice(deviceId); + GAINPUT_ASSERT(device); + Stream* stream = allocator->New(32, *allocator); + stream->Write(uint8_t(DevCmdSetDeviceButton)); + stream->Write(uint8_t(device->GetType())); + stream->Write(uint8_t(device->GetIndex())); + stream->Write(uint32_t(deviceButton)); + stream->Write(newValue); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); + return true; + } + +private: + const InputManager* inputManager_; +}; + +static HashMap devUserButtonListeners(GetDefaultAllocator()); +DevDeviceButtonListener* devDeviceButtonListener = 0; +ListenerId devDeviceButtonListenerId; + + + +void +SendDevice(const InputDevice* device, Stream* stream, NetConnection*) +{ + const DeviceId deviceId = device->GetDeviceId(); + stream->Reset(); + stream->Write(uint8_t(DevCmdDevice)); + stream->Write(uint32_t(device->GetDeviceId())); + char deviceName[64]; + snprintf(deviceName, 64, "%s%d", device->GetTypeName(), device->GetIndex()); + stream->Write(uint8_t(strlen(deviceName))); + stream->Write(deviceName, strlen(deviceName)); + stream->SeekBegin(0); + SendMessage(*stream); + + for (DeviceButtonId buttonId = 0; buttonId < device->GetInputState()->GetButtonCount(); ++buttonId) + { + if (device->IsValidButtonId(buttonId)) + { + stream->Reset(); + stream->Write(uint8_t(DevCmdDeviceButton)); + stream->Write(uint32_t(deviceId)); + stream->Write(uint32_t(buttonId)); + char buttonName[128]; + const size_t len= device->GetButtonName(buttonId, buttonName, 128); + stream->Write(uint8_t(len)); + if (len) + stream->Write(buttonName, len); + stream->Write(uint8_t(device->GetButtonType(buttonId))); + stream->SeekBegin(0); + SendMessage(*stream); + } + } +} + +void +SendMap(const InputMap* map, Stream* stream, NetConnection*) +{ + stream->Reset(); + stream->Write(uint8_t(DevCmdMap)); + stream->Write(uint32_t(map->GetId())); + stream->Write(uint8_t(strlen(map->GetName()))); + stream->Write(map->GetName(), strlen(map->GetName())); + stream->SeekBegin(0); + SendMessage(*stream); + + DeviceButtonSpec mappings[32]; + for (UserButtonId buttonId = 0; buttonId < 1000; ++buttonId) + { + if (map->IsMapped(buttonId)) + { + const size_t n = map->GetMappings(buttonId, mappings, 32); + for (size_t i = 0; i < n; ++i) + { + stream->Reset(); + stream->Write(uint8_t(DevCmdUserButton)); + stream->Write(uint32_t(map->GetId())); + stream->Write(uint32_t(buttonId)); + stream->Write(uint32_t(mappings[i].deviceId)); + stream->Write(uint32_t(mappings[i].buttonId)); + stream->Write(map->GetFloat(buttonId)); + stream->SeekBegin(0); + SendMessage(*stream); + } + } + } +} + +void +DevSetHttp(bool enable) +{ + useHttp = enable; +} + +void +DevInit(InputManager* manager) +{ + if (devListener) + { + return; + } + +#if defined(GAINPUT_PLATFORM_WIN) + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) + { + GAINPUT_ASSERT(false); + } +#endif + + inputManager = manager; + allocator = &manager->GetAllocator(); + + NetAddress address("0.0.0.0", 1211); + devListener = allocator->New(address, *allocator); + + if (devListener->Start(false)) + { + GAINPUT_LOG("TOOL: Listening...\n"); + } + else + { + GAINPUT_LOG("TOOL: Unable to listen\n"); + allocator->Delete(devListener); + devListener = 0; + } + + +} + +void +DevShutdown(const InputManager* manager) +{ + if (inputManager != manager) + { + return; + } + + if (devConnection) + { + devConnection->Close(); + allocator->Delete(devConnection); + devConnection = 0; + } + + if (devListener) + { + devListener->Stop(); + allocator->Delete(devListener); + devListener = 0; + } + + for (HashMap::iterator it = devUserButtonListeners.begin(); + it != devUserButtonListeners.end(); + ++it) + { + allocator->Delete(it->second); + } + + if (devDeviceButtonListener) + { + inputManager->RemoveListener(devDeviceButtonListenerId); + allocator->Delete(devDeviceButtonListener); + devDeviceButtonListener = 0; + } + +#if defined(GAINPUT_PLATFORM_WIN) + WSACleanup(); +#endif +} + + +void +DevUpdate(InputDeltaState* delta) +{ + if (!devConnection && devListener) + { + devConnection = devListener->Accept(); + if (!useHttp && devConnection) + { + GAINPUT_LOG("GAINPUT: New connection\n"); + Stream* stream = allocator->New(1024, *allocator); + + stream->Write(uint8_t(DevCmdHello)); + stream->Write(uint32_t(DevProtocolVersion)); + stream->Write(uint32_t(GetLibVersion())); + stream->SeekBegin(0); + SendMessage(*stream); + + allocator->Delete(stream); + } + } + + if (!devConnection) + { + return; + } + + if (useHttp) + { + Stream* stream = allocator->New(128, *allocator); + stream->Reset(); + size_t received = devConnection->Receive(*stream, 128); + if (received > 0) + { + char* buf = (char*)allocator->Allocate(received+1); + stream->Read(buf, received); + buf[received] = 0; + int touchDevice; + int id; + float x; + float y; + int down; + sscanf(buf, "GET /%i/%i/%f/%f/%i HTTP", &touchDevice, &id, &x, &y, &down); + //GAINPUT_LOG("Touch device #%d state #%d: %f/%f - %d\n", touchDevice, id, x, y, down); + allocator->Deallocate(buf); + + allocator->Delete(stream); + devConnection->Close(); + allocator->Delete(devConnection); + devConnection = 0; + + const DeviceId deviceId = inputManager->FindDeviceId(InputDevice::DT_TOUCH, touchDevice); + InputDevice* device = inputManager->GetDevice(deviceId); + GAINPUT_ASSERT(device); + GAINPUT_ASSERT(device->GetInputState()); + GAINPUT_ASSERT(device->GetPreviousInputState()); + HandleButton(*device, *device->GetInputState(), delta, Touch0Down + id*4, down != 0); + HandleAxis(*device, *device->GetInputState(), delta, Touch0X + id*4, x); + HandleAxis(*device, *device->GetInputState(), delta, Touch0Pressure + id*4, float(down)*1.0f); + } + } + else + { + Stream* stream = allocator->New(1024, *allocator); + bool readFailed = false; + while (ReadMessage(*stream, readFailed)) + { + uint8_t cmd; + stream->Read(cmd); + + if (cmd == DevCmdGetAllInfos) + { + devSendInfos = true; + + int count = 0; + // Send existing devices + for (InputManager::const_iterator it = inputManager->begin(); + it != inputManager->end(); + ++it) + { + const InputDevice* device = it->second; + SendDevice(device, stream, devConnection); + ++count; + } + GAINPUT_LOG("GAINPUT: Sent %d devices.\n", count); + + // Send existing maps + count = 0; + for (Array::const_iterator it = devMaps.begin(); + it != devMaps.end(); + ++it) + { + const InputMap* map = *it; + SendMap(map, stream, devConnection); + ++count; + } + GAINPUT_LOG("GAINPUT: Sent %d maps.\n", count); + } + else if (cmd == DevCmdStartDeviceSync) + { + uint8_t deviceType; + uint8_t deviceIndex; + stream->Read(deviceType); + stream->Read(deviceIndex); + const DeviceId deviceId = inputManager->FindDeviceId(InputDevice::DeviceType(deviceType), deviceIndex); + InputDevice* device = inputManager->GetDevice(deviceId); + GAINPUT_ASSERT(device); + device->SetSynced(true); + devSyncedDevices.push_back(deviceId); + GAINPUT_LOG("GAINPUT: Starting to sync device #%d.\n", deviceId); + } + else if (cmd == DevCmdSetDeviceButton) + { + uint8_t deviceType; + uint8_t deviceIndex; + uint32_t deviceButtonId; + stream->Read(deviceType); + stream->Read(deviceIndex); + stream->Read(deviceButtonId); + const DeviceId deviceId = inputManager->FindDeviceId(InputDevice::DeviceType(deviceType), deviceIndex); + InputDevice* device = inputManager->GetDevice(deviceId); + GAINPUT_ASSERT(device); + GAINPUT_ASSERT(device->GetInputState()); + GAINPUT_ASSERT(device->GetPreviousInputState()); + if (device->GetButtonType(deviceButtonId) == BT_BOOL) + { + uint8_t value; + stream->Read(value); + bool boolValue = (0 != value); + HandleButton(*device, *device->GetInputState(), delta, deviceButtonId, boolValue); + } + else + { + float value; + stream->Read(value); + HandleAxis(*device, *device->GetInputState(), delta, deviceButtonId, value); + } + } + } + + bool pingFailed = false; + if (devFrame % 100 == 0 && devConnection->IsConnected()) + { + stream->Reset(); + stream->Write(uint8_t(DevCmdPing)); + stream->SeekBegin(0); + if (SendMessage(*stream) == 0) + { + pingFailed = true; + } + } + + allocator->Delete(stream); + + if (readFailed || !devConnection->IsConnected() || pingFailed) + { + GAINPUT_LOG("GAINPUT: Disconnected\n"); + devConnection->Close(); + allocator->Delete(devConnection); + devConnection = 0; + + for (Array::iterator it = devSyncedDevices.begin(); + it != devSyncedDevices.end(); + ++it) + { + InputDevice* device = inputManager->GetDevice(*it); + GAINPUT_ASSERT(device); + device->SetSynced(false); + GAINPUT_LOG("GAINPUT: Stopped syncing device #%d.\n", *it); + } + devSyncedDevices.clear(); + + if (devDeviceButtonListener) + { + inputManager->RemoveListener(devDeviceButtonListenerId); + allocator->Delete(devDeviceButtonListener); + devDeviceButtonListener = 0; + } + + return; + } + } + + ++devFrame; +} + +void +DevNewMap(InputMap* inputMap) +{ + if (devMaps.find(inputMap) != devMaps.end()) + { + return; + } + devMaps.push_back(inputMap); + + DevUserButtonListener* listener = allocator->New(inputMap); + devUserButtonListeners[inputMap] = listener; + inputMap->AddListener(listener); + + if (devConnection && devSendInfos) + { + Stream* stream = allocator->New(1024, *allocator); + SendMap(inputMap, stream, devConnection); + allocator->Delete(stream); + } +} + +void +DevNewUserButton(InputMap* inputMap, UserButtonId userButton, DeviceId device, DeviceButtonId deviceButton) +{ + if (!devConnection || devMaps.find(inputMap) == devMaps.end() || !devSendInfos) + { + return; + } + + Stream* stream = allocator->New(1024, *allocator); + stream->Write(uint8_t(DevCmdUserButton)); + stream->Write(uint32_t(inputMap->GetId())); + stream->Write(uint32_t(userButton)); + stream->Write(uint32_t(device)); + stream->Write(uint32_t(deviceButton)); + stream->Write(inputMap->GetFloat(userButton)); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); +} + +void +DevRemoveUserButton(InputMap* inputMap, UserButtonId userButton) +{ + if (!devConnection || !devSendInfos) + { + return; + } + + Stream* stream = allocator->New(1024, *allocator); + stream->Write(uint8_t(DevCmdRemoveUserButton)); + stream->Write(uint32_t(inputMap->GetId())); + stream->Write(uint32_t(userButton)); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); +} + +void +DevRemoveMap(InputMap* inputMap) +{ + if (!devConnection || devMaps.find(inputMap) == devMaps.end()) + { + return; + } + + if (devSendInfos) + { + Stream* stream = allocator->New(1024, *allocator); + stream->Write(uint8_t(DevCmdRemoveMap)); + stream->Write(uint32_t(inputMap->GetId())); + stream->SeekBegin(0); + SendMessage(*stream); + allocator->Delete(stream); + } + + devMaps.erase(devMaps.find(inputMap)); + + HashMap::iterator it = devUserButtonListeners.find(inputMap); + if (it != devUserButtonListeners.end()) + { + allocator->Delete(it->second); + devUserButtonListeners.erase(it->first); + } +} + +void +DevNewDevice(InputDevice* device) +{ + if (devDevices.find(device) != devDevices.end()) + { + return; + } + devDevices.push_back(device); + + if (devConnection && devSendInfos) + { + Stream* stream = allocator->New(1024, *allocator); + SendDevice(device, stream, devConnection); + allocator->Delete(stream); + } +} + +void +DevConnect(InputManager* manager, const char* ip, unsigned port) +{ + if (devConnection) + { + devConnection->Close(); + allocator->Delete(devConnection); + } + + if (devListener) + { + devListener->Stop(); + allocator->Delete(devListener); + devListener = 0; + } + + inputManager = manager; + allocator = &manager->GetAllocator(); + + devConnection = allocator->New(NetAddress(ip, port)); + + if (!devConnection->Connect(false) || !devConnection->IsConnected()) + { + devConnection->Close(); + allocator->Delete(devConnection); + devConnection = 0; + GAINPUT_LOG("GAINPUT: Unable to connect to %s:%d.\n", ip, port); + } + + GAINPUT_LOG("GAINPUT: Connected to %s:%d.\n", ip, port); + + if (!devDeviceButtonListener) + { + devDeviceButtonListener = allocator->New(manager); + devDeviceButtonListenerId = inputManager->AddListener(devDeviceButtonListener); + } +} + +void +DevStartDeviceSync(DeviceId deviceId) +{ + if (devSyncedDevices.find(deviceId) != devSyncedDevices.end() + || !devConnection) + return; + devSyncedDevices.push_back(deviceId); + + const InputDevice* device = inputManager->GetDevice(deviceId); + GAINPUT_ASSERT(device); + + Stream* stream = allocator->New(32, *allocator); + stream->Write(uint8_t(DevCmdStartDeviceSync)); + stream->Write(uint8_t(device->GetType())); + stream->Write(uint8_t(device->GetIndex())); + stream->SeekBegin(0); + SendMessage(*stream); + + // Send device state + for (DeviceButtonId buttonId = 0; buttonId < device->GetInputState()->GetButtonCount(); ++buttonId) + { + if (device->IsValidButtonId(buttonId)) + { + stream->Reset(); + stream->Write(uint8_t(DevCmdSetDeviceButton)); + stream->Write(uint8_t(device->GetType())); + stream->Write(uint8_t(device->GetIndex())); + stream->Write(uint32_t(buttonId)); + if (device->GetButtonType(buttonId) == BT_BOOL) + { + stream->Write(uint8_t(device->GetBool(buttonId))); + } + else + { + stream->Write(device->GetFloat(buttonId)); + } + stream->SeekBegin(0); + SendMessage(*stream); + } + } + + allocator->Delete(stream); +} + +} + +#else + +namespace gainput +{ +void +DevSetHttp(bool /*enable*/) +{ } +} + +#endif + diff --git a/lib/source/gainput/dev/GainputDev.h b/lib/source/gainput/dev/GainputDev.h new file mode 100644 index 00000000..f7072f9a --- /dev/null +++ b/lib/source/gainput/dev/GainputDev.h @@ -0,0 +1,52 @@ + +#ifndef GAINPUTDEV_H_ +#define GAINPUTDEV_H_ + +#ifdef GAINPUT_DEV + +#include "GainputDevProtocol.h" + +namespace gainput +{ + +void DevInit(InputManager* inputManager); +void DevShutdown(const InputManager* inputManager); +void DevUpdate(InputDeltaState* delta); +void DevNewMap(InputMap* inputMap); +void DevNewUserButton(InputMap* inputMap, UserButtonId userButton, DeviceId device, DeviceButtonId deviceButton); +void DevRemoveUserButton(InputMap* inputMap, UserButtonId userButton); +void DevRemoveMap(InputMap* inputMap); +void DevNewDevice(InputDevice* device); +void DevConnect(InputManager* inputManager, const char* ip, unsigned port); +void DevStartDeviceSync(DeviceId deviceId); + +} + +#define GAINPUT_DEV_INIT(inputManager) DevInit(inputManager) +#define GAINPUT_DEV_SHUTDOWN(inputManager) DevShutdown(inputManager) +#define GAINPUT_DEV_UPDATE(delta) DevUpdate(delta) +#define GAINPUT_DEV_NEW_MAP(inputMap) DevNewMap(inputMap) +#define GAINPUT_DEV_NEW_USER_BUTTON(inputMap, userButton, device, deviceButton) DevNewUserButton(inputMap, userButton, device, deviceButton) +#define GAINPUT_DEV_REMOVE_USER_BUTTON(inputMap, userButton) DevRemoveUserButton(inputMap, userButton) +#define GAINPUT_DEV_REMOVE_MAP(inputMap) DevRemoveMap(inputMap) +#define GAINPUT_DEV_NEW_DEVICE(device) DevNewDevice(device) +#define GAINPUT_DEV_CONNECT(inputManager, ip, port) DevConnect(inputManager, ip, port) +#define GAINPUT_DEV_START_SYNC(deviceId) DevStartDeviceSync(deviceId) + +#else + +#define GAINPUT_DEV_INIT(inputManager) +#define GAINPUT_DEV_SHUTDOWN(inputManager) +#define GAINPUT_DEV_UPDATE(delta) +#define GAINPUT_DEV_NEW_MAP(inputMap) +#define GAINPUT_DEV_NEW_USER_BUTTON(inputMap, userButton, device, deviceButton) +#define GAINPUT_DEV_REMOVE_USER_BUTTON(inputMap, userButton) +#define GAINPUT_DEV_REMOVE_MAP(inputMap) +#define GAINPUT_DEV_NEW_DEVICE(device) +#define GAINPUT_DEV_CONNECT(inputManager, ip, port) +#define GAINPUT_DEV_START_SYNC(device) + +#endif + +#endif + diff --git a/lib/source/gainput/dev/GainputDevProtocol.h b/lib/source/gainput/dev/GainputDevProtocol.h new file mode 100644 index 00000000..47183f7e --- /dev/null +++ b/lib/source/gainput/dev/GainputDevProtocol.h @@ -0,0 +1,28 @@ +#ifndef GAINPUTDEVPROTOCOL_H_ +#define GAINPUTDEVPROTOCOL_H_ + +namespace gainput +{ + +enum DevCmd +{ + DevCmdHello, + DevCmdDevice, + DevCmdDeviceButton, + DevCmdMap, + DevCmdRemoveMap, + DevCmdUserButton, + DevCmdRemoveUserButton, + DevCmdPing, + DevCmdUserButtonChanged, + DevCmdGetAllInfos, + DevCmdStartDeviceSync, + DevCmdSetDeviceButton, +}; + +const static unsigned DevProtocolVersion = 0x3; + +} + +#endif + diff --git a/lib/source/gainput/dev/GainputMemoryStream.cpp b/lib/source/gainput/dev/GainputMemoryStream.cpp new file mode 100644 index 00000000..a7d0aac6 --- /dev/null +++ b/lib/source/gainput/dev/GainputMemoryStream.cpp @@ -0,0 +1,91 @@ +#include + +#if defined(GAINPUT_DEV) || defined(GAINPUT_ENABLE_RECORDER) +#include "GainputMemoryStream.h" + +namespace gainput { + +MemoryStream::MemoryStream(void* data, size_t length, size_t capacity, bool ownership) : + data_(data), + length_(length), + capacity_(capacity), + ownership_(ownership), + position_(0) +{ + // empty +} + +MemoryStream::MemoryStream(size_t capacity, Allocator& allocator) : + allocator_(&allocator), + length_(0), + capacity_(capacity), + ownership_(true), + position_(0) +{ + data_ = this->allocator_->Allocate(capacity_); +} + +MemoryStream::~MemoryStream() +{ + if (ownership_) + { + assert(allocator_); + allocator_->Deallocate(data_); + } +} + +size_t +MemoryStream::Read(void* dest, size_t readLength) +{ + assert(position_ + readLength <= length_); + memcpy(dest, (void*)( (uint8_t*)data_ + position_), readLength); + position_ += readLength; + return readLength; +} + +size_t +MemoryStream::Write(const void* src, size_t writeLength) +{ + assert(position_ + writeLength <= capacity_); + memcpy((void*)( (uint8_t*)data_ + position_), src, writeLength); + position_ += writeLength; + length_ += writeLength; + return writeLength; +} + +bool +MemoryStream::SeekBegin(int offset) +{ + if (offset < 0) + { + return false; + } + position_ = offset; + return true; +} + +bool +MemoryStream::SeekCurrent(int offset) +{ + if (offset + position_ > length_) + { + return false; + } + position_ += offset; + return true; +} + +bool +MemoryStream::SeekEnd(int offset) +{ + if (offset > 0) + { + return false; + } + position_ = length_ + offset; + return true; +} + +} +#endif + diff --git a/lib/source/gainput/dev/GainputMemoryStream.h b/lib/source/gainput/dev/GainputMemoryStream.h new file mode 100644 index 00000000..248b62ed --- /dev/null +++ b/lib/source/gainput/dev/GainputMemoryStream.h @@ -0,0 +1,49 @@ +#ifndef GAINPUTMEMORYSTREAM_H_ +#define GAINPUTMEMORYSTREAM_H_ + +#include "GainputStream.h" + +namespace gainput { + +class MemoryStream : public Stream +{ +public: + MemoryStream(void* data, size_t length, size_t capacity, bool ownership = false); + MemoryStream(size_t capacity, Allocator& allocator = GetDefaultAllocator()); + ~MemoryStream(); + + size_t Read(void* dest, size_t readLength); + size_t Write(const void* src, size_t writeLength); + + size_t GetSize() const { return length_; } + size_t GetLeft() const { return length_ - position_; } + + bool SeekBegin(int offset); + bool SeekCurrent(int offset); + bool SeekEnd(int offset); + + virtual void Reset() { length_ = 0; position_ = 0; } + + bool IsEof() const + { + return position_ >= length_; + } + + void* GetData() { return data_; } + size_t GetPosition() const { return position_; } + +private: + Allocator* allocator_; + void* data_; + size_t length_; + size_t capacity_; + bool ownership_; + + size_t position_; + +}; + +} + +#endif + diff --git a/lib/source/gainput/dev/GainputNetAddress.cpp b/lib/source/gainput/dev/GainputNetAddress.cpp new file mode 100644 index 00000000..fdcc3f79 --- /dev/null +++ b/lib/source/gainput/dev/GainputNetAddress.cpp @@ -0,0 +1,44 @@ +#include + +#ifdef GAINPUT_DEV +#include "GainputNetAddress.h" + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_WIN) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) +#include +#include +#include +#endif + +namespace gainput { + +NetAddress::NetAddress(const char* ip, unsigned port) +{ +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + struct in_addr inp; + if (!inet_aton(ip, &inp)) + { + assert(false); + return; + } + addr.sin_addr.s_addr = inp.s_addr; +#elif defined(GAINPUT_PLATFORM_WIN) + addr.sin_addr.s_addr = inet_addr(ip); +#endif + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); +} + +NetAddress::NetAddress(const struct sockaddr_in& rhs) +{ + addr.sin_family = rhs.sin_family; + addr.sin_addr.s_addr = rhs.sin_addr.s_addr; + addr.sin_port = rhs.sin_port; +} + +} +#endif +#endif + diff --git a/lib/source/gainput/dev/GainputNetAddress.h b/lib/source/gainput/dev/GainputNetAddress.h new file mode 100644 index 00000000..9e3f26cc --- /dev/null +++ b/lib/source/gainput/dev/GainputNetAddress.h @@ -0,0 +1,53 @@ +#ifndef GAINPUTADDRESS_H_ +#define GAINPUTADDRESS_H_ + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) +#include +#include +#include +#elif defined(GAINPUT_PLATFORM_WIN) +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#include +#include +#endif + +namespace gainput { + +class NetAddress +{ +public: + NetAddress(const char* ip, unsigned port); + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_WIN) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + NetAddress(const struct sockaddr_in& rhs); + + const struct sockaddr_in& GetAddr() const { return addr; } + struct sockaddr_in& GetAddr() { return addr; } +#endif + + NetAddress& operator = (const NetAddress& rhs); + +private: +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_WIN) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + struct sockaddr_in addr; +#endif + +}; + + +inline +NetAddress& +NetAddress::operator = (const NetAddress& rhs) +{ +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_WIN) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + addr.sin_family = rhs.addr.sin_family; + addr.sin_addr.s_addr = rhs.addr.sin_addr.s_addr; + addr.sin_port = rhs.addr.sin_port; +#endif + return *this; +} + +} + +#endif + diff --git a/lib/source/gainput/dev/GainputNetConnection.cpp b/lib/source/gainput/dev/GainputNetConnection.cpp new file mode 100644 index 00000000..fa9e4a02 --- /dev/null +++ b/lib/source/gainput/dev/GainputNetConnection.cpp @@ -0,0 +1,261 @@ +#include + +#ifdef GAINPUT_DEV +#include "GainputNetAddress.h" +#include "GainputNetConnection.h" + +#include "GainputStream.h" + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_WIN) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) +#include +#include +#include +#include +#include +#include +#endif + +namespace gainput { + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) +NetConnection::NetConnection(const NetAddress& address, Allocator& allocator) : + allocator(allocator), + address(address), + fd(-1) +{ +} + +NetConnection::NetConnection(const NetAddress& remoteAddress, int fd, Allocator& allocator) : + allocator(allocator), + address(remoteAddress), + fd(fd) +{ +} + +NetConnection::~NetConnection() +{ + Close(); +} + +bool +NetConnection::Connect(bool shouldBlock) +{ + assert(fd == -1); + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + { + return false; + } + + if (connect(fd, (struct sockaddr*)&address.GetAddr(), sizeof(struct sockaddr_in)) == -1) + { + return false; + } + + if (!shouldBlock && fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + { + return false; + } + + return true; +} + +bool +NetConnection::IsConnected() const +{ + return fd != -1; +} + +void +NetConnection::Close() +{ + if (fd == -1) + { + return; + } + + close(fd); + fd = -1; +} +#elif defined(GAINPUT_PLATFORM_WIN) +NetConnection::NetConnection(const NetAddress& address, Allocator& allocator) : + allocator(allocator), + address(address), + fd(INVALID_SOCKET) +{ +} + +NetConnection::NetConnection(const NetAddress& remoteAddress, SOCKET fd, Allocator& allocator) : + allocator(allocator), + address(remoteAddress), + fd(fd) +{ +} + +NetConnection::~NetConnection() +{ + Close(); +} + +bool +NetConnection::Connect(bool shouldBlock) +{ + assert(fd == INVALID_SOCKET); + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == INVALID_SOCKET) + { + return false; + } + + if (connect(fd, (struct sockaddr*)&address.GetAddr(), sizeof(struct sockaddr_in)) == -1) + { + return false; + } + + if (!shouldBlock) + { + u_long NonBlock = 1; + if (ioctlsocket(fd, FIONBIO, &NonBlock) == SOCKET_ERROR) + { + return false; + } + } + + return true; +} + +bool +NetConnection::IsConnected() const +{ + return fd != INVALID_SOCKET; +} + +void +NetConnection::Close() +{ + if (fd == INVALID_SOCKET) + { + return; + } + + closesocket(fd); + fd = INVALID_SOCKET; +} +#endif + +bool +NetConnection::IsReady(bool read, bool write) +{ + fd_set read_fds; + fd_set write_fds; + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + if (read) + { + FD_SET(fd, &read_fds); + } + if (write) + { + FD_SET(fd, &write_fds); + } + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + int rc = select(sizeof(read_fds)*8, &read_fds, &write_fds, 0, &timeout); + if (rc == -1) + { + return false; + } + + bool result = false; + if (rc > 0) + { + result = true; + if (read && !FD_ISSET(fd, &read_fds)) + { + result = false; + } + if (write && !FD_ISSET(fd, &write_fds)) + { + result = false; + } + } + + return result; +} + +size_t +NetConnection::Send(const void* buf, size_t length) +{ + if (!IsConnected()) + { + return 0; + } + assert(IsConnected()); +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) + std::ptrdiff_t const result = send(fd, buf, length, MSG_NOSIGNAL); + if (result == -1 && errno == EPIPE) +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC)|| defined(GAINPUT_PLATFORM_TVOS) + std::ptrdiff_t const result = send(fd, buf, length, 0); + if (result == -1 && errno == EPIPE) +#elif defined(GAINPUT_PLATFORM_WIN) + std::ptrdiff_t const result = send(fd, (const char*)buf, length, 0); + if (result == -1) +#endif + { + Close(); + } + return result >= 0 ? static_cast(result) : 0; +} + +size_t +NetConnection::Send(Stream& stream) +{ + const size_t length = stream.GetLeft(); + char* buf = (char*)allocator.Allocate(length); + stream.Read(buf, length); + size_t sentLength = Send(buf, length); + allocator.Deallocate(buf); + return sentLength; +} + +size_t +NetConnection::Receive(void* buffer, size_t length) +{ + assert(IsConnected()); +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + ssize_t receivedLength = recv(fd, (char*)buffer, length, 0); + if (receivedLength == -1) +#elif defined(GAINPUT_PLATFORM_WIN) + int receivedLength = recv(fd, (char*)buffer, length, 0); + if (receivedLength == SOCKET_ERROR) +#endif + { + return 0; + } + return receivedLength; +} + +size_t +NetConnection::Receive(Stream& stream, size_t maxLength) +{ + char* buf = (char*)allocator.Allocate(maxLength); + size_t receivedLength = Receive(buf, maxLength); + stream.Write(buf, receivedLength); + stream.SeekBegin(0); + allocator.Deallocate(buf); + return receivedLength; +} + +} + +#endif +#endif + diff --git a/lib/source/gainput/dev/GainputNetConnection.h b/lib/source/gainput/dev/GainputNetConnection.h new file mode 100644 index 00000000..661ba328 --- /dev/null +++ b/lib/source/gainput/dev/GainputNetConnection.h @@ -0,0 +1,47 @@ +#ifndef GAINPUTCONNECTION_H_ +#define GAINPUTCONNECTION_H_ + +#include "GainputNetAddress.h" + +namespace gainput { + +class Stream; + +class NetConnection +{ +public: + NetConnection(const NetAddress& address, Allocator& allocator = GetDefaultAllocator()); +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + NetConnection(const NetAddress& remoteAddress, int fd, Allocator& allocator = GetDefaultAllocator()); +#elif defined(GAINPUT_PLATFORM_WIN) + NetConnection(const NetAddress& remoteAddress, SOCKET fd, Allocator& allocator = GetDefaultAllocator()); +#endif + ~NetConnection(); + + bool Connect(bool shouldBlock); + void Close(); + bool IsConnected() const; + bool IsReady(bool read, bool write); + + size_t Send(const void* buffer, size_t length); + size_t Send(Stream& stream); + + size_t Receive(void* buffer, size_t length); + size_t Receive(Stream& stream, size_t maxLength); + +private: + Allocator& allocator; + NetAddress address; + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + int fd; +#elif defined(GAINPUT_PLATFORM_WIN) + SOCKET fd; +#endif + +}; + +} + +#endif + diff --git a/lib/source/gainput/dev/GainputNetListener.cpp b/lib/source/gainput/dev/GainputNetListener.cpp new file mode 100644 index 00000000..b5cbbab7 --- /dev/null +++ b/lib/source/gainput/dev/GainputNetListener.cpp @@ -0,0 +1,185 @@ +#include + +#ifdef GAINPUT_DEV +#include "GainputNetAddress.h" +#include "GainputNetConnection.h" +#include "GainputNetListener.h" + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) +#include +#include + +namespace gainput { + +NetListener::NetListener(const NetAddress& address, Allocator& allocator) : + address(address), + allocator(allocator), + blocking(true), + fd(-1) +{ + +} + +NetListener::~NetListener() +{ + Stop(); +} + +bool +NetListener::Start(bool shouldBlock) +{ + assert(fd == -1); + + blocking = shouldBlock; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) + { + return false; + } + + if (!shouldBlock && fcntl(fd, F_SETFL, O_NONBLOCK) == -1) + { + return false; + } + + const int sock_reuse_optval = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &sock_reuse_optval, sizeof(sock_reuse_optval)); + + if (bind(fd, (struct sockaddr*)&address.GetAddr(), sizeof(struct sockaddr_in)) == -1) + { + return false; + } + + if (listen(fd, 50) == -1) + { + return false; + } + + return true; +} + +void +NetListener::Stop() +{ + if (fd == -1) + { + return; + } + + close(fd); + fd = -1; +} + +NetConnection* +NetListener::Accept() +{ + assert(fd != -1); + struct sockaddr_in addr; + socklen_t addr_len = sizeof(struct sockaddr_in); + + int remoteFd = accept(fd, (struct sockaddr*)&addr, &addr_len); + if (remoteFd == -1) + { + return 0; + } + + if (!blocking) + { + fcntl(remoteFd, F_SETFL, O_NONBLOCK); + } + + NetAddress remoteAddress(addr); + NetConnection* connection = allocator.New(remoteAddress, remoteFd, allocator); + + return connection; +} + +} + +#elif defined(GAINPUT_PLATFORM_WIN) + +namespace gainput { + +NetListener::NetListener(const NetAddress& address, Allocator& allocator) : + address(address), + allocator(allocator), + blocking(true), + listenSocket(INVALID_SOCKET) +{ + +} + +NetListener::~NetListener() +{ + Stop(); +} + +bool +NetListener::Start(bool shouldBlock) +{ + assert(listenSocket == INVALID_SOCKET); + listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listenSocket == INVALID_SOCKET) + { + return false; + } + + if (!shouldBlock) + { + u_long NonBlock = 1; + if (ioctlsocket(listenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR) + { + return false; + } + } + + if (bind(listenSocket, (struct sockaddr*)&address.GetAddr(), sizeof(struct sockaddr_in)) == SOCKET_ERROR) + { + closesocket(listenSocket); + return false; + } + + if (listen(listenSocket, SOMAXCONN ) == SOCKET_ERROR) + { + closesocket(listenSocket); + return false; + } + + return true; +} + +void +NetListener::Stop() +{ + if (listenSocket == INVALID_SOCKET) + { + return; + } + + closesocket(listenSocket); + listenSocket = INVALID_SOCKET; +} + +NetConnection* +NetListener::Accept() +{ + assert(listenSocket != INVALID_SOCKET); + struct sockaddr_in addr; + int addr_len = sizeof(struct sockaddr_in); + SOCKET remoteSocket = accept(listenSocket, (struct sockaddr*)&addr, &addr_len); + if (remoteSocket == INVALID_SOCKET) + { + return 0; + } + NetAddress remoteAddress(addr); + NetConnection* connection = allocator.New(remoteAddress, remoteSocket, allocator); + return connection; +} + +} + + +#endif +#endif + diff --git a/lib/source/gainput/dev/GainputNetListener.h b/lib/source/gainput/dev/GainputNetListener.h new file mode 100644 index 00000000..0f465c68 --- /dev/null +++ b/lib/source/gainput/dev/GainputNetListener.h @@ -0,0 +1,38 @@ +#ifndef GAINPUTLISTENER_H_ +#define GAINPUTLISTENER_H_ + +#include +#include "GainputNetAddress.h" + +namespace gainput { + +class NetConnection; + +class NetListener +{ +public: + NetListener(const NetAddress& address, Allocator& allocator = GetDefaultAllocator()); + ~NetListener(); + + bool Start(bool shouldBlock); + void Stop(); + + NetConnection* Accept(); + +private: + NetAddress address; + Allocator& allocator; + bool blocking; + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) || defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_MAC) || defined(GAINPUT_PLATFORM_TVOS) + int fd; +#elif defined(GAINPUT_PLATFORM_WIN) + SOCKET listenSocket; +#endif + +}; + +} + +#endif + diff --git a/lib/source/gainput/dev/GainputStream.h b/lib/source/gainput/dev/GainputStream.h new file mode 100644 index 00000000..e0d63af4 --- /dev/null +++ b/lib/source/gainput/dev/GainputStream.h @@ -0,0 +1,145 @@ +#ifndef GAINPUTSTREAM_H_ +#define GAINPUTSTREAM_H_ + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_ANDROID) +#include +#include +#elif defined(GAINPUT_PLATFORM_WIN) +#include +typedef unsigned __int16 uint16_t; +typedef __int16 int16_t; +typedef unsigned __int32 uint32_t; +typedef __int32 int32_t; +#endif + +namespace gainput { + +class Stream +{ +public: + virtual ~Stream() { } + + virtual size_t Read(void* dest, size_t length) = 0; + virtual size_t Write(const void* src, size_t length) = 0; + + virtual size_t GetSize() const = 0; + virtual size_t GetLeft() const = 0; + + virtual bool SeekBegin(int offset) = 0; + virtual bool SeekCurrent(int offset) = 0; + virtual bool SeekEnd(int offset) = 0; + + virtual void Reset() = 0; + + virtual bool IsEof() const = 0; + + template size_t Read(T& dest) { return Read(&dest, sizeof(T)); } + template size_t Write(const T& src) { return Write(&src, sizeof(T)); } + +}; + +template<> +inline +size_t +Stream::Read(uint16_t& dest) +{ + const size_t result = Read(&dest, sizeof(dest)); + dest = ntohs(dest); + return result; +} + +template<> +inline +size_t +Stream::Read(int16_t& dest) +{ + const size_t result = Read(&dest, sizeof(dest)); + dest = ntohs((uint16_t)dest); + return result; +} + +template<> +inline +size_t +Stream::Read(uint32_t& dest) +{ + const size_t result = Read(&dest, sizeof(dest)); + dest = ntohl(dest); + return result; +} + +template<> +inline +size_t +Stream::Read(int32_t& dest) +{ + const size_t result = Read(&dest, sizeof(dest)); + dest = ntohl((uint32_t)dest); + return result; +} + +template<> +inline +size_t +Stream::Read(float& dest) +{ + const size_t result = Read(&dest, sizeof(dest)); + const uint32_t tmpInt = ntohl(*(uint32_t*)&dest); + const float* tmpFloatP = (float*)&tmpInt; + dest = *tmpFloatP; + return result; +} + + +template<> +inline +size_t +Stream::Write(const uint16_t& src) +{ + const uint16_t tmp = htons(src); + return Write(&tmp, sizeof(tmp)); +} + +template<> +inline +size_t +Stream::Write(const int16_t& src) +{ + const int16_t tmp = htons((uint16_t)src); + return Write(&tmp, sizeof(tmp)); +} + +template<> +inline +size_t +Stream::Write(const uint32_t& src) +{ + const uint32_t tmp = htonl(src); + return Write(&tmp, sizeof(tmp)); +} + +template<> +inline +size_t +Stream::Write(const int32_t& src) +{ + const int32_t tmp = htonl((uint32_t)src); + return Write(&tmp, sizeof(tmp)); +} + +template<> +inline +size_t +Stream::Write(const float& src) +{ + const uint32_t tmpInt = htonl(*(uint32_t*)&src); + const float* tmpFloatP = (float*)&tmpInt; + const float tmp = *tmpFloatP; + return Write(&tmp, sizeof(tmp)); +} + + +} + +#endif + diff --git a/lib/source/gainput/gainput.cpp b/lib/source/gainput/gainput.cpp new file mode 100644 index 00000000..fed2eb36 --- /dev/null +++ b/lib/source/gainput/gainput.cpp @@ -0,0 +1,383 @@ + +#include + +/** +\mainpage Gainput Documentation + +Gainput is a C++ Open Source input library for games. It collects input data from different devices (like keyboards, mice or gamepads) and makes the inputs accessible through a unified interface. On top of that, inputs can be mapped to user-defined buttons and other advanced features are offered. Gainput aims to be a one-stop solution to acquire input from players for your game. + +If there are any problems, please report them on GitHub or contact: gainput -a-t- johanneskuhlmann.de. + +These pages are Gainput's API documentation. In order to download Gainput go to the Gainput website. + +\section contents Contents +- \ref page_start +- \ref page_building +- \ref page_platforms +- \ref page_dependencies +- \ref page_faq + +\section using Using Gainput +A minimal usage sample: + +\code +#include + +// Define your user buttons somewhere global +enum Button +{ + ButtonConfirm +}; + +// Setting up Gainput +gainput::InputManager manager; +const gainput::DeviceId mouseId = manager.CreateDevice(); + +manager.SetDisplaySize(width, height); + +gainput::InputMap map(manager); +map.MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + +while (game_running) +{ + // Call every frame + manager.Update(); + + // May have to call platform-specific event-handling functions here. + + // Check button state + if (map.GetBoolWasDown(ButtonConfirm)) + { + // Confirmed! + } +} +\endcode + + +\section license License + +Gainput is licensed under the MIT license: + +\include "LICENSE" + + + +\page page_start Getting Started +\tableofcontents +This page gives an overview on how to get Gainput into your game and use it for getting your players' input. + + +\section sect_obtaining Obtaining Gainput +Gainput can be obtained in source from GitHub. + +\section sect_building Building +Build Gainput as described on the \ref page_building page. + + +\section sect_integrating Integration Into Your Project +To begin with, your project should link to the dynamic or static version of the Gainput library. On Linux, the files are \c libgainput.so (dynamic library) and \c libgainputstatic.a (static library). On Windows, the filenames are \c gainput.lib (used with \c gainput.dll) and \c gainputstatic.lib. In case you decide to use the dynamic library, make sure to distribute the dynamic library together with your executable. + +To have the API available, you have to include Gainput's main header file: + +\code +#include +\endcode + +You should have the \c lib/include/ folder as an include folder in your project settings for this to work. The file includes most of Gainput's other header files so that you shouldn't need to include anything else. + + +\section sect_setup Setting up Gainput +Gainput's most important class is gainput::InputManager. You should create one that you use throughout your game. Create some input devices using gainput::InputManager::CreateDevice(). And then, during your game loop, call gainput::InputManager::Update() every frame. + +Some platform-specific function calls may be necessary, like gainput::InputManager::HandleMessage(). + +On top of your gainput::InputManager, you should create at least one gainput::InputMap and map some device-specific buttons to your custom user buttons using gainput::InputMap::MapBool() or gainput::InputMap::MapFloat(). This will allow you to more conventienly provide alternative input methods or enable the player to change button mappings. + + +\section sect_using Using Gainput +After everything has been set up, use gainput::InputMap::GetBool() or gainput::InputMap::GetFloat() (and related functions) anywhere in your game to get player input. + + + + +\page page_building Building +Gainput is built using CMake which makes it easy to build the library. + +Simply run these commands: +-# `mkdir build` +-# `cd build` +-# `cmake ..` +-# `make` + +There are the regular CMake build configurations from which you choose by adding one these to the `cmake` call, for example: +- `-DCMAKE_BUILD_TYPE=Debug` +- `-DCMAKE_BUILD_TYPE=Release` + +Building Gainput as shown above, will build a dynamic-link library, a static-link library, and all samples. The executables can be found in the \c build/ folder. + + +\section sect_defines Build Configuration Defines +There is a number of defines that determine what is included in the library and how it behaves. Normally, most of these are set by the build scripts or in gainput.h, but it may be necessary to set these when doing custom builds or modifying the build process. All defines must be set during compilation of the library itself. + +Name | Description +-----|------------ +\c GAINPUT_DEBUG | Enables debugging of the library itself, i.e. enables a lot of internal logs and checks. +\c GAINPUT_DEV | Enables the built-in development tool server that external tools or other Gainput instances can connect to. +\c GAINPUT_ENABLE_ALL_GESTURES | Enables all gestures. Note that there is also an individual define for each gesture (see gainput::InputGesture). +\c GAINPUT_ENABLE_RECORDER | Enables recording of inputs. +\c GAINPUT_LIB_BUILD | Should be set if Gainput is being built as a library. + + +\section sect_android_build Android NDK +In order to cross-compile for Android, the build has to be configured differently. + +Naturally, the Android NDK must be installed. Make sure that \c ANDROID_NDK_PATH is set to the absolute path to your installation. And then follow these steps: + +- Run `cmake -DCMAKE_TOOLCHAIN_FILE=../extern/cmake/android.toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -DANDROID_ABI="armeabi-v7a" -DANDROID_NATIVE_API_LEVEL=android-19 -DANDROID_STL="gnustl_static"` +- Build as above. + +Executing these commands will also yield both a dynamic and static library in the \c build/ folder. + + +\page sample_fw Sample Framework +This framework makes it easier to provide succinct samples by taking care of the platform-dependent window creation/destruction. + +In a proper project, everything that is handled by the sample framework should be handled for you by some other library or engine. The sample framework is not meant for production use. + +\example ../../../samples/basic/basicsample_win.cpp +Shows the most basic initialization and usage of Gainput. It has separate implementations for all supported platforms. + +\example ../../../samples/basic/basicsample_linux.cpp +Shows the most basic initialization and usage of Gainput. It has separate implementations for all supported platforms. + +\example ../../../samples/basic/basicsample_android.cpp +Shows the most basic initialization and usage of Gainput. It has separate implementations for all supported platforms. + +\example ../../../samples/dynamic/dynamicsample.cpp +Shows how to let the user dynamically change button mappings as well as how to load and save mappings. Uses the \ref sample_fw. + +\example ../../../samples/gesture/gesturesample.cpp +Shows how to use input gestures. Also shows how to implement your own custom input device. Uses the \ref sample_fw. + +\example ../../../samples/listener/listenersample.cpp +Shows how to use device button listeners as well as user button listeners. Uses the \ref sample_fw. + +\example ../../../samples/recording/recordingsample.cpp +Shows how to record, play and serialize/deserialize input sequences. Uses the \ref sample_fw. + +\example ../../../samples/sync/syncsample.cpp +Shows how to connect two Gainput instances to each other and send the device state over the network. Uses the \ref sample_fw. Works only in the \c dev build configuration. + + +\page page_platforms Platform Notes +\tableofcontents + +\section platform_matrix Device Support Matrix + +Platform \\ Device | Built-In | Keyboard | Mouse | Pad | Touch +-------------------|----------|----------|-------|-----|------ +Android NDK | YES | YES | | | YES +iOS | YES | | | YES | YES +Linux | | STD RAW | STD RAW | YES | | +Mac OS X | | YES | YES | YES | | +Windows | | STD RAW | STD RAW | YES | | + +What the entries mean: +- YES: This device is supported on this platform (basically, the same as STD). +- STD: This device is supported in standard variant on this platform (gainput::InputDevice::DV_STANDARD). +- RAW: This device is supported in raw variant on this platform (gainput::InputDevice::DV_RAW). + + +\section platform_android Android NDK +The keyboard support is limited to hardware keyboards, including, for example, hardware home buttons. + +\section platform_linux Linux +Evdev is used for the raw input variants. Evdev has permission issues on some Linux distributions where the devices (\c /dev/input/event*) are only readable by root or a specific group. If a raw device's state is gainput::InputDevice::DS_UNAVAILABLE this may very well be the cause. + +These gamepads have been tested and are explicitly supported: +- Microsoft X-Box 360 pad +- Sony PLAYSTATION(R)3 Controller + +\section platform_osx Mac OS X +These gamepads have been tested and are explicitly supported: +- Microsoft X-Box 360 pad +- Sony PLAYSTATION(R)3 Controller + +\section platform_windows Windows +The gamepad support is implemented using XINPUT which is Microsoft's most current API for such devices. However, that means that only Xbox 360 pads and compatible devices are supported. + + +\page page_dependencies Dependencies +Gainput has very few external dependencies in order to make it as self-contained as possible. Normally, the only extra piece of software you might have to install is CMake. Anything else should come with your IDE (or regular platform SDK). + +\section sect_libs Libraries +Most importantly, Gainput does not depend on the STL or any other unnecessary helper libraries. Input is acquired using the following methods: + +Android NDK: All input is acquired through the NDK. Native App Glue is used for most inputs. + +iOS: +- CoreMotion framework for built-in device sensors +- GameController framework for gamepad inputs +- UIKit for touch inputs + +Linux: +- the X11 message loop is used for keyboard and mouse +- the kernel's joystick API is used for pads + +Mac OS X: +- AppKit for mouse +- IOKit's IOHIDManager for keyboard and gamepads + +Windows: +- the Win32 message loop is used for keyboard and mouse +- XINPUT is used for gamepads + +\section sect_building Building +Gainput is built using CMake + + +\page page_faq FAQ + +\tableofcontents + +\section faq0 Why another library when input is included in most engines/libraries? +There are lots of other ways to acquire input, most are part of more complete engines or more comprehensive libraries. For one, Gainput is meant for those who are using something without input capabilities (for example, pure rendering engines) or those who are developing something themselves and want to skip input handling. + +In the long run, Gainput aims to be better and offer more advanced features than built-in input solutions. That's the reason why more advanced features, like input recording/playback, remote syncing, gestures and external tool support, are already part of the library. + + +\page page_devprotocol Development Tool Protocol +If Gainput is built with \c GAINPUT_DEV defined, it features a server that external tools can connect to obtain information on devices, mappings and button states. The underlying protocol is TCP/IP and the default port 1211. + +The following messages are defined: + +\code +hello +{ + uint8_t cmd + uint32_t protocolVersion + uint32_t libVersion +} + +device +{ + uint8_t cmd + uint32_t deviceId + string name +} + +device button +{ + uint8_t cmd + uint32_t deviceId + uint32_t buttonId + string name + uint8_t type +} + +map +{ + uint8_t cmd + uint32_t mapId + string name +} + +remove map +{ + uint8_t cmd + uint32_t mapId +} + +user button +{ + uint8_t cmd + uint32_t mapId + uint32_t buttonId + uint32_t deviceId + uint32_t deviceButtonId + float value +} + +remove user button +{ + uint8_t cmd + uint32_t mapId + uint32_t buttonId +} + +user button changed +{ + uint8_t cmd + uint32_t mapId + uint32_t buttonId + uint8_t type + uint8_t/float value +} + +ping +{ + uint8_t cmd +} + +get all infos +{ + uint8_t cmd +} + +start device sync +{ + uint8_t cmd + uint8_t deviceType + uint8_t deviceIndex +} + +set device button +{ + uint8_t cmd + uint8_t deviceType + uint8_t deviceIndex + uint32_t deviceButtonId + uint8_t/float value +} +\endcode + +The message IDs (\c cmd) are defined in GainputDevProtocol.h. + +Each message is prefaced with a \c uint8_t that specifies the message's length. + +All integers are in network byte order. + +Strings are represented like this: + +\code +{ + uint8_t length + char text[length] // without the trailing \0 +} +\endcode + +*/ + +namespace gainput +{ + +const char* +GetLibName() +{ + return "Gainput"; +} + +uint32_t +GetLibVersion() +{ + return ((1 << GAINPUT_VER_MAJOR_SHIFT) | (0) ); +} + +const char* +GetLibVersionString() +{ + return "1.0.0"; +} + +} + diff --git a/lib/source/gainput/gestures/GainputButtonStickGesture.cpp b/lib/source/gainput/gestures/GainputButtonStickGesture.cpp new file mode 100644 index 00000000..7577797e --- /dev/null +++ b/lib/source/gainput/gestures/GainputButtonStickGesture.cpp @@ -0,0 +1,74 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_BUTTON_STICK_GESTURE +#include +#include + +namespace gainput +{ + +ButtonStickGesture::ButtonStickGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index) +{ + negativeAxis_.buttonId = InvalidDeviceButtonId; + positiveAxis_.buttonId = InvalidDeviceButtonId; + + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(previousState_); +} + +ButtonStickGesture::~ButtonStickGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +ButtonStickGesture::Initialize(DeviceId negativeAxisDevice, DeviceButtonId negativeAxis, + DeviceId positiveAxisDevice, DeviceButtonId positiveAxis) +{ + negativeAxis_.deviceId = negativeAxisDevice; + negativeAxis_.buttonId = negativeAxis; + positiveAxis_.deviceId = positiveAxisDevice; + positiveAxis_.buttonId = positiveAxis; +} + +void +ButtonStickGesture::InternalUpdate(InputDeltaState* delta) +{ + if (negativeAxis_.buttonId == InvalidDeviceButtonId + || positiveAxis_.buttonId == InvalidDeviceButtonId) + { + return; + } + + const InputDevice* negativeDevice = manager_.GetDevice(negativeAxis_.deviceId); + GAINPUT_ASSERT(negativeDevice); + const bool isDown = negativeDevice->GetBool(negativeAxis_.buttonId); + + const InputDevice* positiveDevice = manager_.GetDevice(positiveAxis_.deviceId); + GAINPUT_ASSERT(positiveDevice); + const bool isDown2 = positiveDevice->GetBool(positiveAxis_.buttonId); + + if (isDown && !isDown2) + { + HandleAxis(*this, *state_, delta, ButtonStickAxis, -1.0f); + } + else if (!isDown && isDown2) + { + HandleAxis(*this, *state_, delta, ButtonStickAxis, 1.0f); + } + else + { + HandleAxis(*this, *state_, delta, ButtonStickAxis, 0.0f); + } +} + +} + +#endif + diff --git a/lib/source/gainput/gestures/GainputDoubleClickGesture.cpp b/lib/source/gainput/gestures/GainputDoubleClickGesture.cpp new file mode 100644 index 00000000..932968c2 --- /dev/null +++ b/lib/source/gainput/gestures/GainputDoubleClickGesture.cpp @@ -0,0 +1,124 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_DOUBLE_CLICK_GESTURE +#include +#include + +namespace gainput +{ + +DoubleClickGesture::DoubleClickGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index), + timeSpan_(1000), + firstClickTime_(0), + clicksRegistered_(0), + clicksTargetCount_(2) +{ + actionButton_.buttonId = InvalidDeviceButtonId; + xAxis_.buttonId = InvalidDeviceButtonId; + yAxis_.buttonId = InvalidDeviceButtonId; + + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(previousState_); +} + +DoubleClickGesture::~DoubleClickGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +DoubleClickGesture::Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, uint64_t timeSpan) +{ + actionButton_.deviceId = actionButtonDevice; + actionButton_.buttonId = actionButton; + xAxis_.buttonId = InvalidDeviceButtonId; + yAxis_.buttonId = InvalidDeviceButtonId; + timeSpan_ = timeSpan; +} + +void +DoubleClickGesture::Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, float xTolerance, + DeviceId yAxisDevice, DeviceButtonId yAxis, float yTolerance, + uint64_t timeSpan) +{ + actionButton_.deviceId = actionButtonDevice; + actionButton_.buttonId = actionButton; + xAxis_.deviceId = xAxisDevice; + xAxis_.buttonId = xAxis; + xTolerance_ = xTolerance; + yAxis_.deviceId = yAxisDevice; + yAxis_.buttonId = yAxis; + yTolerance_ = yTolerance; + timeSpan_ = timeSpan; +} + +void +DoubleClickGesture::InternalUpdate(InputDeltaState* delta) +{ + if (actionButton_.buttonId == InvalidDeviceButtonId) + { + return; + } + + const InputDevice* actionDevice = manager_.GetDevice(actionButton_.deviceId); + GAINPUT_ASSERT(actionDevice); + + bool positionValid = false; + float clickX = 0.0f; + float clickY = 0.0f; + if (xAxis_.buttonId != InvalidDeviceButtonId && yAxis_.buttonId != InvalidDeviceButtonId) + { + const InputDevice* xAxisDevice = manager_.GetDevice(xAxis_.deviceId); + GAINPUT_ASSERT(xAxisDevice); + clickX = xAxisDevice->GetFloat(xAxis_.buttonId); + const InputDevice* yAxisDevice = manager_.GetDevice(yAxis_.deviceId); + GAINPUT_ASSERT(yAxisDevice); + clickY = yAxisDevice->GetFloat(yAxis_.buttonId); + positionValid = true; + } + + if (actionDevice->GetBoolPrevious(actionButton_.buttonId) + && !actionDevice->GetBool(actionButton_.buttonId)) + { + if (clicksRegistered_ == 0) + { + firstClickTime_ = manager_.GetTime(); + firstClickX_ = clickX; + firstClickY_ = clickY; + } + ++clicksRegistered_; + } + + if (firstClickTime_ + timeSpan_ < manager_.GetTime()) + { + clicksRegistered_ = 0; + } + + if (positionValid && clicksRegistered_ > 1 + && (Abs(clickX - firstClickX_) > xTolerance_ || Abs(clickY - firstClickY_) > yTolerance_)) + { + clicksRegistered_ = 0; + } + + if (clicksRegistered_ >= clicksTargetCount_) + { + clicksRegistered_ = 0; + HandleButton(*this, *state_, delta, DoubleClickTriggered, true); + } + else + { + HandleButton(*this, *state_, delta, DoubleClickTriggered, false); + } +} + +} + +#endif + diff --git a/lib/source/gainput/gestures/GainputHoldGesture.cpp b/lib/source/gainput/gestures/GainputHoldGesture.cpp new file mode 100644 index 00000000..8867942b --- /dev/null +++ b/lib/source/gainput/gestures/GainputHoldGesture.cpp @@ -0,0 +1,134 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_HOLD_GESTURE +#include +#include + +namespace gainput +{ + +HoldGesture::HoldGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index), + oneShotReset_(true), + firstDownTime_(0) +{ + actionButton_.buttonId = InvalidDeviceButtonId; + xAxis_.buttonId = InvalidDeviceButtonId; + yAxis_.buttonId = InvalidDeviceButtonId; + + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(previousState_); +} + +HoldGesture::~HoldGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +HoldGesture::Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, bool oneShot, uint64_t timeSpan) +{ + actionButton_.deviceId = actionButtonDevice; + actionButton_.buttonId = actionButton; + xAxis_.buttonId = InvalidDeviceButtonId; + yAxis_.buttonId = InvalidDeviceButtonId; + oneShot_ = oneShot; + timeSpan_ = timeSpan; +} + +void +HoldGesture::Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, float xTolerance, + DeviceId yAxisDevice, DeviceButtonId yAxis, float yTolerance, + bool oneShot, + uint64_t timeSpan) +{ + actionButton_.deviceId = actionButtonDevice; + actionButton_.buttonId = actionButton; + xAxis_.deviceId = xAxisDevice; + xAxis_.buttonId = xAxis; + xTolerance_ = xTolerance; + yAxis_.deviceId = yAxisDevice; + yAxis_.buttonId = yAxis; + yTolerance_ = yTolerance; + oneShot_ = oneShot; + timeSpan_ = timeSpan; +} + +void +HoldGesture::InternalUpdate(InputDeltaState* delta) +{ + if (actionButton_.buttonId == InvalidDeviceButtonId) + { + return; + } + + const InputDevice* actionDevice = manager_.GetDevice(actionButton_.deviceId); + GAINPUT_ASSERT(actionDevice); + + bool positionValid = false; + float posX = 0.0f; + float posY = 0.0f; + if (xAxis_.buttonId != InvalidDeviceButtonId && yAxis_.buttonId != InvalidDeviceButtonId) + { + const InputDevice* xAxisDevice = manager_.GetDevice(xAxis_.deviceId); + GAINPUT_ASSERT(xAxisDevice); + posX = xAxisDevice->GetFloat(xAxis_.buttonId); + const InputDevice* yAxisDevice = manager_.GetDevice(yAxis_.deviceId); + GAINPUT_ASSERT(yAxisDevice); + posY = yAxisDevice->GetFloat(yAxis_.buttonId); + positionValid = true; + } + + if (actionDevice->GetBool(actionButton_.buttonId)) + { + if (firstDownTime_ == 0) + { + firstDownTime_ = manager_.GetTime(); + firstDownX_ = posX; + firstDownY_ = posY; + } + } + else + { + oneShotReset_ = true; + firstDownTime_ = 0; + HandleButton(*this, *state_, delta, HoldTriggered, false); + return; + } + + if (positionValid + && (Abs(posX - firstDownX_) > xTolerance_ || Abs(posY - firstDownY_) > yTolerance_)) + { + firstDownTime_ = 0; + } + + bool downLongEnough = firstDownTime_ + timeSpan_ <= manager_.GetTime(); + + if (oneShot_) + { + if (downLongEnough && oneShotReset_) + { + HandleButton(*this, *state_, delta, HoldTriggered, true); + oneShotReset_ = false; + } + else + { + HandleButton(*this, *state_, delta, HoldTriggered, false); + } + } + else + { + HandleButton(*this, *state_, delta, HoldTriggered, downLongEnough); + } +} + +} + +#endif + diff --git a/lib/source/gainput/gestures/GainputPinchGesture.cpp b/lib/source/gainput/gestures/GainputPinchGesture.cpp new file mode 100644 index 00000000..2959150f --- /dev/null +++ b/lib/source/gainput/gestures/GainputPinchGesture.cpp @@ -0,0 +1,120 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_PINCH_GESTURE +#include +#include +#include + +namespace gainput +{ + +PinchGesture::PinchGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index), + pinching_(false) +{ + downButton_.buttonId = InvalidDeviceButtonId; + xAxis_.buttonId = InvalidDeviceButtonId; + yAxis_.buttonId = InvalidDeviceButtonId; + downButton2_.buttonId = InvalidDeviceButtonId; + xAxis2_.buttonId = InvalidDeviceButtonId; + yAxis2_.buttonId = InvalidDeviceButtonId; + + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 2); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 2); + GAINPUT_ASSERT(previousState_); +} + +PinchGesture::~PinchGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +PinchGesture::Initialize(DeviceId downDevice, DeviceButtonId downButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, + DeviceId yAxisDevice, DeviceButtonId yAxis, + DeviceId down2Device, DeviceButtonId downButton2, + DeviceId xAxis2Device, DeviceButtonId xAxis2, + DeviceId yAxis2Device, DeviceButtonId yAxis2) +{ + downButton_.deviceId = downDevice; + downButton_.buttonId = downButton; + xAxis_.deviceId = xAxisDevice; + xAxis_.buttonId = xAxis; + yAxis_.deviceId = yAxisDevice; + yAxis_.buttonId = yAxis; + downButton2_.deviceId = down2Device; + downButton2_.buttonId = downButton2; + xAxis2_.deviceId = xAxis2Device; + xAxis2_.buttonId = xAxis2; + yAxis2_.deviceId = yAxis2Device; + yAxis2_.buttonId = yAxis2; +} + +void +PinchGesture::InternalUpdate(InputDeltaState* delta) +{ + if (downButton_.buttonId == InvalidDeviceButtonId + || xAxis_.buttonId == InvalidDeviceButtonId + || yAxis_.buttonId == InvalidDeviceButtonId + || downButton2_.buttonId == InvalidDeviceButtonId + || xAxis2_.buttonId == InvalidDeviceButtonId + || yAxis2_.buttonId == InvalidDeviceButtonId) + { + return; + } + + const InputDevice* downDevice = manager_.GetDevice(downButton_.deviceId); + GAINPUT_ASSERT(downDevice); + const bool isDown = downDevice->GetBool(downButton_.buttonId); + + const InputDevice* downDevice2 = manager_.GetDevice(downButton2_.deviceId); + GAINPUT_ASSERT(downDevice2); + const bool isDown2 = downDevice2->GetBool(downButton2_.buttonId); + + if (!isDown || !isDown2) + { + HandleButton(*this, *state_, delta, PinchTriggered, false); + pinching_ = false; + return; + } + + HandleButton(*this, *state_, delta, PinchTriggered, true); + + const InputDevice* xAxisDevice = manager_.GetDevice(xAxis_.deviceId); + GAINPUT_ASSERT(xAxisDevice); + const float posX = xAxisDevice->GetFloat(xAxis_.buttonId); + const InputDevice* yAxisDevice = manager_.GetDevice(yAxis_.deviceId); + GAINPUT_ASSERT(yAxisDevice); + const float posY = yAxisDevice->GetFloat(yAxis_.buttonId); + + const InputDevice* xAxis2Device = manager_.GetDevice(xAxis2_.deviceId); + GAINPUT_ASSERT(xAxis2Device); + const float posX2 = xAxis2Device->GetFloat(xAxis2_.buttonId); + const InputDevice* yAxis2Device = manager_.GetDevice(yAxis2_.deviceId); + GAINPUT_ASSERT(yAxis2Device); + const float posY2 = yAxis2Device->GetFloat(yAxis2_.buttonId); + + const float xd = posX - posX2; + const float yd = posY - posY2; + const float dist = sqrtf(xd*xd + yd*yd); + + if (!pinching_ && dist > 0.0f) + { + pinching_ = true; + initialDistance_ = dist; + HandleAxis(*this, *state_, delta, PinchScale, 1.0f); + return; + } + + HandleAxis(*this, *state_, delta, PinchScale, dist / initialDistance_); +} + +} + +#endif + diff --git a/lib/source/gainput/gestures/GainputRotateGesture.cpp b/lib/source/gainput/gestures/GainputRotateGesture.cpp new file mode 100644 index 00000000..e8b0138d --- /dev/null +++ b/lib/source/gainput/gestures/GainputRotateGesture.cpp @@ -0,0 +1,126 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_ROTATE_GESTURE +#include +#include + +#define _USE_MATH_DEFINES +#include + +namespace gainput +{ + +RotateGesture::RotateGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index), + rotating_(false) +{ + downButton_.buttonId = InvalidDeviceButtonId; + xAxis_.buttonId = InvalidDeviceButtonId; + yAxis_.buttonId = InvalidDeviceButtonId; + downButton2_.buttonId = InvalidDeviceButtonId; + xAxis2_.buttonId = InvalidDeviceButtonId; + yAxis2_.buttonId = InvalidDeviceButtonId; + + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 2); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 2); + GAINPUT_ASSERT(previousState_); +} + +RotateGesture::~RotateGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +RotateGesture::Initialize(DeviceId downDevice, DeviceButtonId downButton, + DeviceId xAxisDevice, DeviceButtonId xAxis, + DeviceId yAxisDevice, DeviceButtonId yAxis, + DeviceId down2Device, DeviceButtonId downButton2, + DeviceId xAxis2Device, DeviceButtonId xAxis2, + DeviceId yAxis2Device, DeviceButtonId yAxis2) +{ + downButton_.deviceId = downDevice; + downButton_.buttonId = downButton; + xAxis_.deviceId = xAxisDevice; + xAxis_.buttonId = xAxis; + yAxis_.deviceId = yAxisDevice; + yAxis_.buttonId = yAxis; + downButton2_.deviceId = down2Device; + downButton2_.buttonId = downButton2; + xAxis2_.deviceId = xAxis2Device; + xAxis2_.buttonId = xAxis2; + yAxis2_.deviceId = yAxis2Device; + yAxis2_.buttonId = yAxis2; +} + +void +RotateGesture::InternalUpdate(InputDeltaState* delta) +{ + if (downButton_.buttonId == InvalidDeviceButtonId + || xAxis_.buttonId == InvalidDeviceButtonId + || yAxis_.buttonId == InvalidDeviceButtonId + || downButton2_.buttonId == InvalidDeviceButtonId + || xAxis2_.buttonId == InvalidDeviceButtonId + || yAxis2_.buttonId == InvalidDeviceButtonId) + { + return; + } + + const InputDevice* downDevice = manager_.GetDevice(downButton_.deviceId); + GAINPUT_ASSERT(downDevice); + const bool isDown = downDevice->GetBool(downButton_.buttonId); + + const InputDevice* downDevice2 = manager_.GetDevice(downButton2_.deviceId); + GAINPUT_ASSERT(downDevice2); + const bool isDown2 = downDevice2->GetBool(downButton2_.buttonId); + + if (!isDown || !isDown2) + { + HandleButton(*this, *state_, delta, RotateTriggered, false); + rotating_ = false; + return; + } + + HandleButton(*this, *state_, delta, RotateTriggered, true); + + const InputDevice* xAxisDevice = manager_.GetDevice(xAxis_.deviceId); + GAINPUT_ASSERT(xAxisDevice); + const float posX = xAxisDevice->GetFloat(xAxis_.buttonId); + const InputDevice* yAxisDevice = manager_.GetDevice(yAxis_.deviceId); + GAINPUT_ASSERT(yAxisDevice); + const float posY = yAxisDevice->GetFloat(yAxis_.buttonId); + + const InputDevice* xAxis2Device = manager_.GetDevice(xAxis2_.deviceId); + GAINPUT_ASSERT(xAxis2Device); + const float posX2 = xAxis2Device->GetFloat(xAxis2_.buttonId); + const InputDevice* yAxis2Device = manager_.GetDevice(yAxis2_.deviceId); + GAINPUT_ASSERT(yAxis2Device); + const float posY2 = yAxis2Device->GetFloat(yAxis2_.buttonId); + + const float angle = atan2f(posY2 - posY, posX2 - posX); + + if (!rotating_) + { + rotating_ = true; + initialAngle_ = angle; + HandleAxis(*this, *state_, delta, RotateAngle, 0.0f); + return; + } + + float currentAngle = angle - initialAngle_; + if (currentAngle < 0.0f) + { + currentAngle += static_cast(M_PI)*2.0f; + } + + HandleAxis(*this, *state_, delta, RotateAngle, currentAngle); +} + +} + +#endif + diff --git a/lib/source/gainput/gestures/GainputSimultaneouslyDownGesture.cpp b/lib/source/gainput/gestures/GainputSimultaneouslyDownGesture.cpp new file mode 100644 index 00000000..ed4d6a28 --- /dev/null +++ b/lib/source/gainput/gestures/GainputSimultaneouslyDownGesture.cpp @@ -0,0 +1,63 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_SIMULTANEOUSLY_DOWN_GESTURE +#include +#include + +namespace gainput +{ + +SimultaneouslyDownGesture::SimultaneouslyDownGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index), + buttons_(manager.GetAllocator()) +{ + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(previousState_); +} + +SimultaneouslyDownGesture::~SimultaneouslyDownGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +SimultaneouslyDownGesture::AddButton(DeviceId device, DeviceButtonId button) +{ + DeviceButtonSpec spec; + spec.deviceId = device; + spec.buttonId = button; + buttons_.push_back(spec); +} + +void +SimultaneouslyDownGesture::ClearButtons() +{ + buttons_.clear(); +} + +void +SimultaneouslyDownGesture::InternalUpdate(InputDeltaState* delta) +{ + bool allDown = !buttons_.empty(); + + for (Array::const_iterator it = buttons_.begin(); + it != buttons_.end() && allDown; + ++it) + { + const InputDevice* device = manager_.GetDevice(it->deviceId); + GAINPUT_ASSERT(device); + allDown = allDown && device->GetBool(it->buttonId); + } + + HandleButton(*this, *state_, delta, SimultaneouslyDownTriggered, allDown); +} + +} + +#endif + diff --git a/lib/source/gainput/gestures/GainputTapGesture.cpp b/lib/source/gainput/gestures/GainputTapGesture.cpp new file mode 100644 index 00000000..3074f338 --- /dev/null +++ b/lib/source/gainput/gestures/GainputTapGesture.cpp @@ -0,0 +1,71 @@ + +#include +#include + +#ifdef GAINPUT_ENABLE_TAP_GESTURE +#include +#include + +namespace gainput +{ + +TapGesture::TapGesture(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputGesture(manager, device, index), + firstDownTime_(0) +{ + actionButton_.buttonId = InvalidDeviceButtonId; + + state_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(state_); + previousState_ = manager_.GetAllocator().New(manager.GetAllocator(), 1); + GAINPUT_ASSERT(previousState_); +} + +TapGesture::~TapGesture() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); +} + +void +TapGesture::Initialize(DeviceId actionButtonDevice, DeviceButtonId actionButton, uint64_t timeSpan) +{ + actionButton_.deviceId = actionButtonDevice; + actionButton_.buttonId = actionButton; + timeSpan_ = timeSpan; +} + +void +TapGesture::InternalUpdate(InputDeltaState* delta) +{ + if (actionButton_.buttonId == InvalidDeviceButtonId) + { + return; + } + + const InputDevice* actionDevice = manager_.GetDevice(actionButton_.deviceId); + GAINPUT_ASSERT(actionDevice); + + HandleButton(*this, *state_, delta, TapTriggered, false); + + if (actionDevice->GetBool(actionButton_.buttonId)) + { + if (firstDownTime_ == 0) + { + firstDownTime_ = manager_.GetTime(); + } + } + else + { + if (firstDownTime_ > 0 && firstDownTime_ + timeSpan_ >= manager_.GetTime()) + { + HandleButton(*this, *state_, delta, TapTriggered, true); + } + firstDownTime_ = 0; + } +} + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboard.cpp b/lib/source/gainput/keyboard/GainputInputDeviceKeyboard.cpp new file mode 100644 index 00000000..27c79e89 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboard.cpp @@ -0,0 +1,191 @@ + +#include +#include + +#include "GainputInputDeviceKeyboardImpl.h" +#include "GainputKeyboardKeyNames.h" +#include +#include +#include + +#if defined(GAINPUT_PLATFORM_LINUX) + #include "GainputInputDeviceKeyboardLinux.h" + #include "GainputInputDeviceKeyboardEvdev.h" +#elif defined(GAINPUT_PLATFORM_WIN) + #include "GainputInputDeviceKeyboardWin.h" + #include "GainputInputDeviceKeyboardWinRaw.h" +#elif defined(GAINPUT_PLATFORM_ANDROID) + #include "GainputInputDeviceKeyboardAndroid.h" +#elif defined(GAINPUT_PLATFORM_MAC) + #include "GainputInputDeviceKeyboardMac.h" +#endif + +#include "GainputInputDeviceKeyboardNull.h" + + +namespace gainput +{ + +InputDeviceKeyboard::InputDeviceKeyboard(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant) : + InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_KEYBOARD): index), + impl_(0), + keyNames_(manager_.GetAllocator()) +{ + state_ = manager.GetAllocator().New(manager.GetAllocator(), KeyCount_); + GAINPUT_ASSERT(state_); + previousState_ = manager.GetAllocator().New(manager.GetAllocator(), KeyCount_); + GAINPUT_ASSERT(previousState_); + +#if defined(GAINPUT_PLATFORM_LINUX) + if (variant == DV_STANDARD) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } + else if (variant == DV_RAW) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } +#elif defined(GAINPUT_PLATFORM_WIN) + if (variant == DV_STANDARD) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } + else if (variant == DV_RAW) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } +#elif defined(GAINPUT_PLATFORM_ANDROID) + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); +#elif defined(GAINPUT_PLATFORM_MAC) + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); +#endif + + if (!impl_) + { + impl_ = manager.GetAllocator().New(manager, device); + } + + GAINPUT_ASSERT(impl_); + + GetKeyboardKeyNames(keyNames_); +} + +InputDeviceKeyboard::~InputDeviceKeyboard() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); + manager_.GetAllocator().Delete(impl_); +} + +void +InputDeviceKeyboard::InternalUpdate(InputDeltaState* delta) +{ + impl_->Update(delta); + + if ((manager_.IsDebugRenderingEnabled() || IsDebugRenderingEnabled()) + && manager_.GetDebugRenderer()) + { + DebugRenderer* debugRenderer = manager_.GetDebugRenderer(); + InputState* state = GetInputState(); + char buf[64]; + const float x = 0.2f; + float y = 0.2f; + for (int i = 0; i < KeyCount_; ++i) + { + if (state->GetBool(i)) + { + GetButtonName(i, buf, 64); + debugRenderer->DrawText(x, y, buf); + y += 0.025f; + } + } + } +} + +InputDevice::DeviceState +InputDeviceKeyboard::InternalGetState() const +{ + return impl_->GetState(); + +} + +InputDevice::DeviceVariant +InputDeviceKeyboard::GetVariant() const +{ + return impl_->GetVariant(); +} + +size_t +InputDeviceKeyboard::GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + GAINPUT_ASSERT(outButtons); + GAINPUT_ASSERT(maxButtonCount > 0); + return CheckAllButtonsDown(outButtons, maxButtonCount, 0, KeyCount_); +} + +size_t +InputDeviceKeyboard::GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + GAINPUT_ASSERT(buffer); + GAINPUT_ASSERT(bufferLength > 0); + HashMap::const_iterator it = keyNames_.find(Key(deviceButton)); + if (it == keyNames_.end()) + { + return 0; + } + strncpy(buffer, it->second, bufferLength); + buffer[bufferLength-1] = 0; + const size_t nameLen = strlen(it->second); + return nameLen >= bufferLength ? bufferLength : nameLen+1; +} + +ButtonType +InputDeviceKeyboard::GetButtonType(DeviceButtonId deviceButton) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + return BT_BOOL; +} + +DeviceButtonId +InputDeviceKeyboard::GetButtonByName(const char* name) const +{ + GAINPUT_ASSERT(name); + for (HashMap::const_iterator it = keyNames_.begin(); + it != keyNames_.end(); + ++it) + { + if (strcmp(name, it->second) == 0) + { + return it->first; + } + } + return InvalidDeviceButtonId; +} + +InputState* +InputDeviceKeyboard::GetNextInputState() +{ + return impl_->GetNextInputState(); +} + +bool +InputDeviceKeyboard::IsTextInputEnabled() const +{ + return impl_->IsTextInputEnabled(); +} + +void +InputDeviceKeyboard::SetTextInputEnabled(bool enabled) +{ + impl_->SetTextInputEnabled(enabled); +} + +char +InputDeviceKeyboard::GetNextCharacter() +{ + return impl_->GetNextCharacter(); +} + +} + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardAndroid.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardAndroid.h new file mode 100644 index 00000000..18cf0d41 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardAndroid.h @@ -0,0 +1,206 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDANDROID_H_ +#define GAINPUTINPUTDEVICEKEYBOARDANDROID_H_ + +#include + +#include "GainputInputDeviceKeyboardImpl.h" +#include + +namespace gainput +{ + +class InputDeviceKeyboardImplAndroid : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplAndroid(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + textInputEnabled_(true), + dialect_(manager_.GetAllocator()), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), KeyCount_), + delta_(0) + { + dialect_[AKEYCODE_SPACE] = KeySpace; + + dialect_[AKEYCODE_APOSTROPHE] = KeyApostrophe; + dialect_[AKEYCODE_COMMA] = KeyComma; + + dialect_[AKEYCODE_0] = Key0; + dialect_[AKEYCODE_1] = Key1; + dialect_[AKEYCODE_2] = Key2; + dialect_[AKEYCODE_3] = Key3; + dialect_[AKEYCODE_4] = Key4; + dialect_[AKEYCODE_5] = Key5; + dialect_[AKEYCODE_6] = Key6; + dialect_[AKEYCODE_7] = Key7; + dialect_[AKEYCODE_8] = Key8; + dialect_[AKEYCODE_9] = Key9; + + dialect_[AKEYCODE_A] = KeyA; + dialect_[AKEYCODE_B] = KeyB; + dialect_[AKEYCODE_C] = KeyC; + dialect_[AKEYCODE_D] = KeyD; + dialect_[AKEYCODE_E] = KeyE; + dialect_[AKEYCODE_F] = KeyF; + dialect_[AKEYCODE_G] = KeyG; + dialect_[AKEYCODE_H] = KeyH; + dialect_[AKEYCODE_I] = KeyI; + dialect_[AKEYCODE_J] = KeyJ; + dialect_[AKEYCODE_K] = KeyK; + dialect_[AKEYCODE_L] = KeyL; + dialect_[AKEYCODE_M] = KeyM; + dialect_[AKEYCODE_N] = KeyN; + dialect_[AKEYCODE_O] = KeyO; + dialect_[AKEYCODE_P] = KeyP; + dialect_[AKEYCODE_Q] = KeyQ; + dialect_[AKEYCODE_R] = KeyR; + dialect_[AKEYCODE_S] = KeyS; + dialect_[AKEYCODE_T] = KeyT; + dialect_[AKEYCODE_U] = KeyU; + dialect_[AKEYCODE_V] = KeyV; + dialect_[AKEYCODE_W] = KeyW; + dialect_[AKEYCODE_X] = KeyX; + dialect_[AKEYCODE_Y] = KeyY; + dialect_[AKEYCODE_Z] = KeyZ; + + dialect_[AKEYCODE_DPAD_LEFT] = KeyLeft; + dialect_[AKEYCODE_DPAD_RIGHT] = KeyRight; + dialect_[AKEYCODE_DPAD_UP] = KeyUp; + dialect_[AKEYCODE_DPAD_DOWN] = KeyDown; + dialect_[AKEYCODE_HOME] = KeyHome; + dialect_[AKEYCODE_DEL] = KeyDelete; + dialect_[AKEYCODE_PAGE_UP] = KeyPageUp; + dialect_[AKEYCODE_PAGE_DOWN] = KeyPageDown; + + dialect_[AKEYCODE_TAB] = KeyTab; + dialect_[AKEYCODE_ENTER] = KeyReturn; + dialect_[AKEYCODE_SHIFT_LEFT] = KeyShiftL; + dialect_[AKEYCODE_ALT_LEFT] = KeyAltL; + dialect_[AKEYCODE_ALT_RIGHT] = KeyAltR; + dialect_[AKEYCODE_MENU] = KeyMenu; + dialect_[AKEYCODE_SHIFT_RIGHT] = KeyShiftR; + + dialect_[AKEYCODE_BACKSLASH] = KeyBackslash; + dialect_[AKEYCODE_LEFT_BRACKET] = KeyBracketLeft; + dialect_[AKEYCODE_RIGHT_BRACKET] = KeyBracketRight; + dialect_[AKEYCODE_NUM] = KeyNumLock; + dialect_[AKEYCODE_GRAVE] = KeyGrave; + + dialect_[AKEYCODE_BACK] = KeyBack; + dialect_[AKEYCODE_SOFT_LEFT] = KeySoftLeft; + dialect_[AKEYCODE_SOFT_RIGHT] = KeySoftRight; + dialect_[AKEYCODE_CALL] = KeyCall; + dialect_[AKEYCODE_ENDCALL] = KeyEndcall; + dialect_[AKEYCODE_STAR] = KeyStar; + dialect_[AKEYCODE_POUND] = KeyPound; + dialect_[AKEYCODE_DPAD_CENTER] = KeyDpadCenter; + dialect_[AKEYCODE_VOLUME_UP] = KeyVolumeUp; + dialect_[AKEYCODE_VOLUME_DOWN] = KeyVolumeDown; + dialect_[AKEYCODE_POWER] = KeyPower; + dialect_[AKEYCODE_CAMERA] = KeyCamera; + dialect_[AKEYCODE_CLEAR] = KeyClear; + dialect_[AKEYCODE_PERIOD] = KeyPeriod; + dialect_[AKEYCODE_SYM] = KeySymbol; + dialect_[AKEYCODE_EXPLORER] = KeyExplorer; + dialect_[AKEYCODE_ENVELOPE] = KeyEnvelope; + dialect_[AKEYCODE_MINUS] = KeyMinus; + dialect_[AKEYCODE_EQUALS] = KeyEquals; + dialect_[AKEYCODE_SEMICOLON] = KeySemicolon; + dialect_[AKEYCODE_SLASH] = KeySlash; + dialect_[AKEYCODE_AT] = KeyAt; + dialect_[AKEYCODE_HEADSETHOOK] = KeyHeadsethook; + dialect_[AKEYCODE_FOCUS] = KeyFocus; + dialect_[AKEYCODE_PLUS] = KeyPlus; + dialect_[AKEYCODE_NOTIFICATION] = KeyNotification; + dialect_[AKEYCODE_SEARCH] = KeySearch; + dialect_[AKEYCODE_MEDIA_PLAY_PAUSE] = KeyMediaPlayPause; + dialect_[AKEYCODE_MEDIA_STOP] = KeyMediaStop; + dialect_[AKEYCODE_MEDIA_NEXT] = KeyMediaNext; + dialect_[AKEYCODE_MEDIA_PREVIOUS] = KeyMediaPrevious; + dialect_[AKEYCODE_MEDIA_REWIND] = KeyMediaRewind; + dialect_[AKEYCODE_MEDIA_FAST_FORWARD] = KeyMediaFastForward; + dialect_[AKEYCODE_MUTE] = KeyMute; + dialect_[AKEYCODE_PICTSYMBOLS] = KeyPictsymbols; + dialect_[AKEYCODE_SWITCH_CHARSET] = KeySwitchCharset; + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + bool IsTextInputEnabled() const { return textInputEnabled_; } + void SetTextInputEnabled(bool enabled) { textInputEnabled_ = enabled; } + + char GetNextCharacter() + { + if (!textBuffer_.CanGet()) + { + return 0; + } + return textBuffer_.Get(); + } + + InputState* GetNextInputState() + { + return &nextState_; + } + + int32_t HandleInput(AInputEvent* event) + { + GAINPUT_ASSERT(event); + GAINPUT_ASSERT(state_); + + if (AInputEvent_getType(event) != AINPUT_EVENT_TYPE_KEY) + { + return 0; + } + + const bool pressed = AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN; + const int32_t keyCode = AKeyEvent_getKeyCode(event); + if (dialect_.count(keyCode)) + { + const DeviceButtonId buttonId = dialect_[keyCode]; + HandleButton(device_, nextState_, delta_, buttonId, pressed); + return 1; + } + + return 0; + } + + DeviceButtonId Translate(int keyCode) const + { + HashMap::const_iterator it = dialect_.find(keyCode); + if (it != dialect_.end()) + { + return it->second; + } + return InvalidDeviceButtonId; + } + +private: + InputManager& manager_; + InputDevice& device_; + bool textInputEnabled_; + RingBuffer textBuffer_; + HashMap dialect_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; +}; + + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardEvdev.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardEvdev.h new file mode 100644 index 00000000..dbb91e9b --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardEvdev.h @@ -0,0 +1,243 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDEVDEV_H_ +#define GAINPUTINPUTDEVICEKEYBOARDEVDEV_H_ + +#include "../GainputHelpersEvdev.h" +#include + +namespace gainput +{ + +class InputDeviceKeyboardImplEvdev : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplEvdev(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + textInputEnabled_(true), + dialect_(manager_.GetAllocator()), + fd_(-1), + state_(&state) + { + unsigned matchingDeviceCount = 0; + for (unsigned i = 0; i < EvdevDeviceCount; ++i) + { + fd_ = open(EvdevDeviceIds[i], O_RDONLY|O_NONBLOCK); + if (fd_ == -1) + { + continue; + } + + EvdevDevice evdev(fd_); + + if (evdev.IsValid()) + { + if (evdev.GetDeviceType() == InputDevice::DT_KEYBOARD) + { + if (matchingDeviceCount == manager_.GetDeviceCountByType(InputDevice::DT_KEYBOARD)) + { + break; + } + ++matchingDeviceCount; + } + } + + close(fd_); + fd_ = -1; + } + + dialect_[KEY_ESC] = KeyEscape; + dialect_[KEY_1] = Key1; + dialect_[KEY_2] = Key2; + dialect_[KEY_3] = Key3; + dialect_[KEY_4] = Key4; + dialect_[KEY_5] = Key5; + dialect_[KEY_6] = Key6; + dialect_[KEY_7] = Key7; + dialect_[KEY_8] = Key8; + dialect_[KEY_9] = Key9; + dialect_[KEY_0] = Key0; + dialect_[KEY_MINUS] = KeyMinus; + dialect_[KEY_EQUAL] = KeyEqual; + dialect_[KEY_BACKSPACE] = KeyBackSpace; + dialect_[KEY_TAB] = KeyTab; + dialect_[KEY_Q] = KeyQ; + dialect_[KEY_W] = KeyW; + dialect_[KEY_E] = KeyE; + dialect_[KEY_R] = KeyR; + dialect_[KEY_T] = KeyT; + dialect_[KEY_Y] = KeyY; + dialect_[KEY_U] = KeyU; + dialect_[KEY_I] = KeyI; + dialect_[KEY_O] = KeyO; + dialect_[KEY_P] = KeyP; + dialect_[KEY_LEFTBRACE] = KeyBraceLeft; + dialect_[KEY_RIGHTBRACE] = KeyBraceRight; + dialect_[KEY_ENTER] = KeyReturn; + dialect_[KEY_LEFTCTRL] = KeyCtrlL; + dialect_[KEY_A] = KeyA; + dialect_[KEY_S] = KeyS; + dialect_[KEY_D] = KeyD; + dialect_[KEY_F] = KeyF; + dialect_[KEY_G] = KeyG; + dialect_[KEY_H] = KeyH; + dialect_[KEY_J] = KeyJ; + dialect_[KEY_K] = KeyK; + dialect_[KEY_L] = KeyL; + dialect_[KEY_SEMICOLON] = KeySemicolon; + dialect_[KEY_APOSTROPHE] = KeyApostrophe; + dialect_[KEY_GRAVE] = KeyGrave; + dialect_[KEY_LEFTSHIFT] = KeyShiftL; + dialect_[KEY_BACKSLASH] = KeyBackslash; + dialect_[KEY_Z] = KeyZ; + dialect_[KEY_X] = KeyX; + dialect_[KEY_C] = KeyC; + dialect_[KEY_V] = KeyV; + dialect_[KEY_B] = KeyB; + dialect_[KEY_N] = KeyN; + dialect_[KEY_M] = KeyM; + dialect_[KEY_COMMA] = KeyComma; + dialect_[KEY_DOT] = KeyPeriod; + dialect_[KEY_SLASH] = KeySlash; + dialect_[KEY_RIGHTSHIFT] = KeyShiftR; + dialect_[KEY_KPASTERISK] = KeyKpMultiply; + dialect_[KEY_LEFTALT] = KeyAltL; + dialect_[KEY_SPACE] = KeySpace; + dialect_[KEY_CAPSLOCK] = KeyCapsLock; + dialect_[KEY_F1] = KeyF1; + dialect_[KEY_F2] = KeyF2; + dialect_[KEY_F3] = KeyF3; + dialect_[KEY_F4] = KeyF4; + dialect_[KEY_F5] = KeyF5; + dialect_[KEY_F6] = KeyF6; + dialect_[KEY_F7] = KeyF7; + dialect_[KEY_F8] = KeyF8; + dialect_[KEY_F9] = KeyF9; + dialect_[KEY_F10] = KeyF10; + dialect_[KEY_NUMLOCK] = KeyNumLock; + dialect_[KEY_SCROLLLOCK] = KeyScrollLock; + dialect_[KEY_KP7] = KeyKpHome; + dialect_[KEY_KP8] = KeyKpUp; + dialect_[KEY_KP9] = KeyKpPageUp; + dialect_[KEY_KPMINUS] = KeyKpSubtract; + dialect_[KEY_KP4] = KeyKpLeft; + dialect_[KEY_KP5] = KeyKpBegin; + dialect_[KEY_KP6] = KeyKpRight; + dialect_[KEY_KPPLUS] = KeyKpAdd; + dialect_[KEY_KP1] = KeyKpEnd; + dialect_[KEY_KP2] = KeyKpDown; + dialect_[KEY_KP3] = KeyKpPageDown; + dialect_[KEY_KP0] = KeyKpInsert; + dialect_[KEY_KPDOT] = KeyKpDelete; + + dialect_[KEY_F11] = KeyF11; + dialect_[KEY_F12] = KeyF12; + dialect_[KEY_KPENTER] = KeyKpEnter; + dialect_[KEY_RIGHTCTRL] = KeyCtrlR; + dialect_[KEY_KPSLASH] = KeyKpDivide; + dialect_[KEY_SYSRQ] = KeySysRq; + dialect_[KEY_RIGHTALT] = KeyAltR; + dialect_[KEY_HOME] = KeyHome; + dialect_[KEY_UP] = KeyUp; + dialect_[KEY_PAGEUP] = KeyPageUp; + dialect_[KEY_LEFT] = KeyLeft; + dialect_[KEY_RIGHT] = KeyRight; + dialect_[KEY_END] = KeyEnd; + dialect_[KEY_DOWN] = KeyDown; + dialect_[KEY_PAGEDOWN] = KeyPageDown; + dialect_[KEY_INSERT] = KeyInsert; + dialect_[KEY_DELETE] = KeyDelete; + dialect_[KEY_MUTE] = KeyMute; + dialect_[KEY_VOLUMEDOWN] = KeyVolumeDown; + dialect_[KEY_VOLUMEUP] = KeyVolumeUp; + dialect_[KEY_POWER] = KeyPower; + dialect_[KEY_PAUSE] = KeyBreak; + dialect_[KEY_KPCOMMA] = KeyKpDelete; + dialect_[KEY_LEFTMETA] = KeySuperL; + dialect_[KEY_RIGHTMETA] = KeySuperR; + + dialect_[KEY_BACK] = KeyBack; + dialect_[KEY_FORWARD] = KeyForward; + dialect_[KEY_NEXTSONG] = KeyMediaNext; + dialect_[KEY_PLAYPAUSE] = KeyMediaPlayPause; + dialect_[KEY_PREVIOUSSONG] = KeyMediaPrevious; + dialect_[KEY_STOPCD] = KeyMediaStop; + dialect_[KEY_CAMERA] = KeyCamera; + dialect_[KEY_COMPOSE] = KeyEnvelope; + dialect_[KEY_REWIND] = KeyMediaRewind; + dialect_[KEY_FASTFORWARD] = KeyMediaFastForward; + } + + ~InputDeviceKeyboardImplEvdev() + { + if (fd_ != -1) + { + close(fd_); + } + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_RAW; + } + + InputDevice::DeviceState GetState() const + { + return fd_ != -1 ? InputDevice::DS_OK : InputDevice::DS_UNAVAILABLE; + } + + void Update(InputDeltaState* delta) + { + if (fd_ < 0) + { + return; + } + + struct input_event event; + + for (;;) + { + int len = read(fd_, &event, sizeof(struct input_event)); + if (len != sizeof(struct input_event)) + { + break; + } + + if (event.type == EV_KEY) + { + if (dialect_.count(event.code)) + { + const DeviceButtonId button = dialect_[event.code]; + HandleButton(device_, *state_, delta, button, bool(event.value)); + } + } + } + } + + bool IsTextInputEnabled() const { return textInputEnabled_; } + void SetTextInputEnabled(bool enabled) { textInputEnabled_ = enabled; } + + char GetNextCharacter() + { + if (!textBuffer_.CanGet()) + { + return 0; + } + return textBuffer_.Get(); + } + +private: + InputManager& manager_; + InputDevice& device_; + bool textInputEnabled_; + RingBuffer textBuffer_; + HashMap dialect_; + int fd_; + InputState* state_; +}; + + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardImpl.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardImpl.h new file mode 100644 index 00000000..710175d4 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardImpl.h @@ -0,0 +1,25 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDIMPL_H_ +#define GAINPUTINPUTDEVICEKEYBOARDIMPL_H_ + +namespace gainput +{ + +class InputDeviceKeyboardImpl +{ +public: + virtual ~InputDeviceKeyboardImpl() { } + virtual InputDevice::DeviceVariant GetVariant() const = 0; + virtual InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } + virtual void Update(InputDeltaState* delta) = 0; + virtual InputState* GetNextInputState() { return 0; } + + virtual bool IsTextInputEnabled() const = 0; + virtual void SetTextInputEnabled(bool enabled) = 0; + virtual char GetNextCharacter() = 0; +}; + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardLinux.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardLinux.h new file mode 100644 index 00000000..daa30fc1 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardLinux.h @@ -0,0 +1,255 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDLINUX_H_ +#define GAINPUTINPUTDEVICEKEYBOARDLINUX_H_ + +#include +#include +#include + +#include "GainputInputDeviceKeyboardImpl.h" +#include + +namespace gainput +{ + +class InputDeviceKeyboardImplLinux : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplLinux(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + textInputEnabled_(true), + dialect_(manager_.GetAllocator()), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), KeyCount_), + delta_(0) + { + // Cf. + dialect_[XK_Escape] = KeyEscape; + dialect_[XK_F1] = KeyF1; + dialect_[XK_F2] = KeyF2; + dialect_[XK_F3] = KeyF3; + dialect_[XK_F4] = KeyF4; + dialect_[XK_F5] = KeyF5; + dialect_[XK_F6] = KeyF6; + dialect_[XK_F7] = KeyF7; + dialect_[XK_F8] = KeyF8; + dialect_[XK_F9] = KeyF9; + dialect_[XK_F10] = KeyF10; + dialect_[XK_F11] = KeyF11; + dialect_[XK_F12] = KeyF12; + dialect_[XK_Print] = KeyPrint; + dialect_[XK_Scroll_Lock] = KeyScrollLock; + dialect_[XK_Pause] = KeyBreak; + + dialect_[XK_space] = KeySpace; + + dialect_[XK_apostrophe] = KeyApostrophe; + dialect_[XK_comma] = KeyComma; + dialect_[XK_minus] = KeyMinus; + dialect_[XK_period] = KeyPeriod; + dialect_[XK_slash] = KeySlash; + + dialect_[XK_0] = Key0; + dialect_[XK_1] = Key1; + dialect_[XK_2] = Key2; + dialect_[XK_3] = Key3; + dialect_[XK_4] = Key4; + dialect_[XK_5] = Key5; + dialect_[XK_6] = Key6; + dialect_[XK_7] = Key7; + dialect_[XK_8] = Key8; + dialect_[XK_9] = Key9; + + dialect_[XK_semicolon] = KeySemicolon; + dialect_[XK_less] = KeyLess; + dialect_[XK_equal] = KeyEqual; + + dialect_[XK_a] = KeyA; + dialect_[XK_b] = KeyB; + dialect_[XK_c] = KeyC; + dialect_[XK_d] = KeyD; + dialect_[XK_e] = KeyE; + dialect_[XK_f] = KeyF; + dialect_[XK_g] = KeyG; + dialect_[XK_h] = KeyH; + dialect_[XK_i] = KeyI; + dialect_[XK_j] = KeyJ; + dialect_[XK_k] = KeyK; + dialect_[XK_l] = KeyL; + dialect_[XK_m] = KeyM; + dialect_[XK_n] = KeyN; + dialect_[XK_o] = KeyO; + dialect_[XK_p] = KeyP; + dialect_[XK_q] = KeyQ; + dialect_[XK_r] = KeyR; + dialect_[XK_s] = KeyS; + dialect_[XK_t] = KeyT; + dialect_[XK_u] = KeyU; + dialect_[XK_v] = KeyV; + dialect_[XK_w] = KeyW; + dialect_[XK_x] = KeyX; + dialect_[XK_y] = KeyY; + dialect_[XK_z] = KeyZ; + + dialect_[XK_bracketleft] = KeyBracketLeft; + dialect_[XK_backslash] = KeyBackslash; + dialect_[XK_bracketright] = KeyBracketRight; + + dialect_[XK_grave] = KeyGrave; + + dialect_[XK_Left] = KeyLeft; + dialect_[XK_Right] = KeyRight; + dialect_[XK_Up] = KeyUp; + dialect_[XK_Down] = KeyDown; + dialect_[XK_Insert] = KeyInsert; + dialect_[XK_Home] = KeyHome; + dialect_[XK_Delete] = KeyDelete; + dialect_[XK_End] = KeyEnd; + dialect_[XK_Page_Up] = KeyPageUp; + dialect_[XK_Page_Down] = KeyPageDown; + + dialect_[XK_Num_Lock] = KeyNumLock; + dialect_[XK_KP_Divide] = KeyKpDivide; + dialect_[XK_KP_Multiply] = KeyKpMultiply; + dialect_[XK_KP_Subtract] = KeyKpSubtract; + dialect_[XK_KP_Add] = KeyKpAdd; + dialect_[XK_KP_Enter] = KeyKpEnter; + dialect_[XK_KP_Insert] = KeyKpInsert; + dialect_[XK_KP_End] = KeyKpEnd; + dialect_[XK_KP_Down] = KeyKpDown; + dialect_[XK_KP_Page_Down] = KeyKpPageDown; + dialect_[XK_KP_Left] = KeyKpLeft; + dialect_[XK_KP_Begin] = KeyKpBegin; + dialect_[XK_KP_Right] = KeyKpRight; + dialect_[XK_KP_Home] = KeyKpHome; + dialect_[XK_KP_Up] = KeyKpUp; + dialect_[XK_KP_Page_Up] = KeyKpPageUp; + dialect_[XK_KP_Delete] = KeyKpDelete; + + dialect_[XK_BackSpace] = KeyBackSpace; + dialect_[XK_Tab] = KeyTab; + dialect_[XK_Return] = KeyReturn; + dialect_[XK_Caps_Lock] = KeyCapsLock; + dialect_[XK_Shift_L] = KeyShiftL; + dialect_[XK_Control_L] = KeyCtrlL; + dialect_[XK_Super_L] = KeySuperL; + dialect_[XK_Alt_L] = KeyAltL; + dialect_[XK_Alt_R] = KeyAltR; + dialect_[XK_Super_R] = KeySuperR; + dialect_[XK_Menu] = KeyMenu; + dialect_[XK_Control_R] = KeyCtrlR; + dialect_[XK_Shift_R] = KeyShiftR; + + dialect_[XK_dead_circumflex] = KeyCircumflex; + dialect_[XK_ssharp] = KeySsharp; + dialect_[XK_dead_acute] = KeyAcute; + dialect_[XK_ISO_Level3_Shift] = KeyAltGr; + dialect_[XK_plus] = KeyPlus; + dialect_[XK_numbersign] = KeyNumbersign; + dialect_[XK_udiaeresis] = KeyUdiaeresis; + dialect_[XK_adiaeresis] = KeyAdiaeresis; + dialect_[XK_odiaeresis] = KeyOdiaeresis; + dialect_[XK_section] = KeySection; + dialect_[XK_aring] = KeyAring; + dialect_[XK_dead_diaeresis] = KeyDiaeresis; + dialect_[XK_twosuperior] = KeyTwosuperior; + dialect_[XK_parenright] = KeyRightParenthesis; + dialect_[XK_dollar] = KeyDollar; + dialect_[XK_ugrave] = KeyUgrave; + dialect_[XK_asterisk] = KeyAsterisk; + dialect_[XK_colon] = KeyColon; + dialect_[XK_exclam] = KeyExclam; + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + bool IsTextInputEnabled() const { return textInputEnabled_; } + void SetTextInputEnabled(bool enabled) { textInputEnabled_ = enabled; } + + char GetNextCharacter() + { + if (!textBuffer_.CanGet()) + { + return 0; + } + return textBuffer_.Get(); + } + + void HandleEvent(XEvent& event) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + if (event.type == KeyPress || event.type == KeyRelease) + { + XKeyEvent& keyEvent = event.xkey; + KeySym keySym = XkbKeycodeToKeysym(keyEvent.display, keyEvent.keycode, 0, 0); + const bool pressed = event.type == KeyPress; + + // Handle key repeat + if (event.type == KeyRelease && XPending(keyEvent.display)) + { + XEvent nextEvent; + XPeekEvent(keyEvent.display, &nextEvent); + if (nextEvent.type == KeyPress + && nextEvent.xkey.keycode == event.xkey.keycode + && nextEvent.xkey.time == event.xkey.time) + { + XNextEvent(keyEvent.display, &nextEvent); + return; + } + } + + if (dialect_.count(keySym)) + { + const DeviceButtonId buttonId = dialect_[keySym]; + HandleButton(device_, nextState_, delta_, buttonId, pressed); + } +#ifdef GAINPUT_DEBUG + else + { + char* str = XKeysymToString(keySym); + GAINPUT_LOG("Unmapped key >> keycode: %d, keysym: %d, str: %s\n", keyEvent.keycode, int(keySym), str); + } +#endif + + if (textInputEnabled_) + { + char buf[32]; + int len = XLookupString(&keyEvent, buf, 32, 0, 0); + if (len == 1) + { + textBuffer_.Put(buf[0]); + } + } + } + } + +private: + InputManager& manager_; + InputDevice& device_; + bool textInputEnabled_; + RingBuffer textBuffer_; + HashMap dialect_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; +}; + + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.cpp b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.cpp new file mode 100644 index 00000000..b32210ea --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.cpp @@ -0,0 +1,271 @@ + +#include + +#ifdef GAINPUT_PLATFORM_MAC + +#include "GainputKeyboardKeyNames.h" +#include +#include +#include + +#include "GainputInputDeviceKeyboardMac.h" + +#import +#import +#import + + +namespace gainput +{ + +extern bool MacIsApplicationKey(); + +namespace { + +static void OnDeviceInput(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) +{ + if (!MacIsApplicationKey()) + { + return; + } + + IOHIDElementRef elem = IOHIDValueGetElement(value); + + InputDeviceKeyboardImplMac* device = reinterpret_cast(inContext); + GAINPUT_ASSERT(device); + + uint16_t scancode = IOHIDElementGetUsage(elem); + + if (IOHIDElementGetUsagePage(elem) != kHIDPage_KeyboardOrKeypad + || scancode == kHIDUsage_KeyboardErrorRollOver + || scancode == kHIDUsage_KeyboardPOSTFail + || scancode == kHIDUsage_KeyboardErrorUndefined + || scancode == kHIDUsage_Keyboard_Reserved) + { + return; + } + + const bool pressed = IOHIDValueGetIntegerValue(value) == 1; + + if (device->dialect_.count(scancode)) + { + const DeviceButtonId buttonId = device->dialect_[scancode]; + InputManager& manager = device->manager_; + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, buttonId, pressed); + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Unmapped key >> scancode: %d\n", int(scancode)); + } +#endif +} + +static void OnDeviceConnected(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) +{ + InputDeviceKeyboardImplMac* device = reinterpret_cast(inContext); + GAINPUT_ASSERT(device); + device->deviceState_ = InputDevice::DS_OK; +} + +static void OnDeviceRemoved(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) +{ + InputDeviceKeyboardImplMac* device = reinterpret_cast(inContext); + GAINPUT_ASSERT(device); + device->deviceState_ = InputDevice::DS_UNAVAILABLE; +} + +} + +InputDeviceKeyboardImplMac::InputDeviceKeyboardImplMac(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + deviceState_(InputDevice::DS_UNAVAILABLE), + textInputEnabled_(true), + dialect_(manager_.GetAllocator()), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), KeyCount_), + delta_(0) +{ + IOHIDManagerRef ioManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone); + + if (CFGetTypeID(ioManager) != IOHIDManagerGetTypeID()) + { + return; + } + + ioManager_ = ioManager; + + static const unsigned kKeyCount = 2; + + int usagePage = kHIDPage_GenericDesktop; + int usage = kHIDUsage_GD_Keyboard; + + CFStringRef keys[kKeyCount] = { + CFSTR(kIOHIDDeviceUsagePageKey), + CFSTR(kIOHIDDeviceUsageKey), + }; + + CFNumberRef values[kKeyCount] = { + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usagePage), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usage), + }; + + CFDictionaryRef matchingDict = CFDictionaryCreate(kCFAllocatorDefault, + (const void **) keys, (const void **) values, kKeyCount, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + for (unsigned i = 0; i < kKeyCount; ++i) + { + CFRelease(keys[i]); + CFRelease(values[i]); + } + + IOHIDManagerSetDeviceMatching(ioManager, matchingDict); + CFRelease(matchingDict); + + IOHIDManagerRegisterDeviceMatchingCallback(ioManager, OnDeviceConnected, this); + IOHIDManagerRegisterDeviceRemovalCallback(ioManager, OnDeviceRemoved, this); + IOHIDManagerRegisterInputValueCallback(ioManager, OnDeviceInput, this); + + IOHIDManagerOpen(ioManager, kIOHIDOptionsTypeNone); + + IOHIDManagerScheduleWithRunLoop(ioManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + dialect_[kHIDUsage_KeyboardEscape] = KeyEscape; + dialect_[kHIDUsage_KeyboardF1] = KeyF1; + dialect_[kHIDUsage_KeyboardF2] = KeyF2; + dialect_[kHIDUsage_KeyboardF3] = KeyF3; + dialect_[kHIDUsage_KeyboardF4] = KeyF4; + dialect_[kHIDUsage_KeyboardF5] = KeyF5; + dialect_[kHIDUsage_KeyboardF6] = KeyF6; + dialect_[kHIDUsage_KeyboardF7] = KeyF7; + dialect_[kHIDUsage_KeyboardF8] = KeyF8; + dialect_[kHIDUsage_KeyboardF9] = KeyF9; + dialect_[kHIDUsage_KeyboardF10] = KeyF10; + dialect_[kHIDUsage_KeyboardF11] = KeyF11; + dialect_[kHIDUsage_KeyboardF12] = KeyF12; + dialect_[kHIDUsage_KeyboardF13] = KeyF13; + dialect_[kHIDUsage_KeyboardF14] = KeyF14; + dialect_[kHIDUsage_KeyboardF15] = KeyF15; + dialect_[kHIDUsage_KeyboardF16] = KeyF16; + dialect_[kHIDUsage_KeyboardF17] = KeyF17; + dialect_[kHIDUsage_KeyboardF18] = KeyF18; + dialect_[kHIDUsage_KeyboardF19] = KeyF19; + dialect_[kHIDUsage_KeyboardPrintScreen] = KeyPrint; + dialect_[kHIDUsage_KeyboardScrollLock] = KeyScrollLock; + dialect_[kHIDUsage_KeyboardPause] = KeyBreak; + + dialect_[kHIDUsage_KeyboardSpacebar] = KeySpace; + + dialect_[kHIDUsage_KeyboardComma] = KeyComma; + dialect_[kHIDUsage_KeyboardHyphen] = KeyMinus; + dialect_[kHIDUsage_KeyboardPeriod] = KeyPeriod; + dialect_[kHIDUsage_KeyboardSlash] = KeySlash; + dialect_[kHIDUsage_KeyboardQuote] = KeyApostrophe; + + dialect_[kHIDUsage_Keyboard0] = Key0; + dialect_[kHIDUsage_Keyboard1] = Key1; + dialect_[kHIDUsage_Keyboard2] = Key2; + dialect_[kHIDUsage_Keyboard3] = Key3; + dialect_[kHIDUsage_Keyboard4] = Key4; + dialect_[kHIDUsage_Keyboard5] = Key5; + dialect_[kHIDUsage_Keyboard6] = Key6; + dialect_[kHIDUsage_Keyboard7] = Key7; + dialect_[kHIDUsage_Keyboard8] = Key8; + dialect_[kHIDUsage_Keyboard9] = Key9; + + dialect_[kHIDUsage_KeyboardSemicolon] = KeySemicolon; + dialect_[kHIDUsage_KeyboardEqualSign] = KeyEqual; + + dialect_[kHIDUsage_KeyboardA] = KeyA; + dialect_[kHIDUsage_KeyboardB] = KeyB; + dialect_[kHIDUsage_KeyboardC] = KeyC; + dialect_[kHIDUsage_KeyboardD] = KeyD; + dialect_[kHIDUsage_KeyboardE] = KeyE; + dialect_[kHIDUsage_KeyboardF] = KeyF; + dialect_[kHIDUsage_KeyboardG] = KeyG; + dialect_[kHIDUsage_KeyboardH] = KeyH; + dialect_[kHIDUsage_KeyboardI] = KeyI; + dialect_[kHIDUsage_KeyboardJ] = KeyJ; + dialect_[kHIDUsage_KeyboardK] = KeyK; + dialect_[kHIDUsage_KeyboardL] = KeyL; + dialect_[kHIDUsage_KeyboardM] = KeyM; + dialect_[kHIDUsage_KeyboardN] = KeyN; + dialect_[kHIDUsage_KeyboardO] = KeyO; + dialect_[kHIDUsage_KeyboardP] = KeyP; + dialect_[kHIDUsage_KeyboardQ] = KeyQ; + dialect_[kHIDUsage_KeyboardR] = KeyR; + dialect_[kHIDUsage_KeyboardS] = KeyS; + dialect_[kHIDUsage_KeyboardT] = KeyT; + dialect_[kHIDUsage_KeyboardU] = KeyU; + dialect_[kHIDUsage_KeyboardV] = KeyV; + dialect_[kHIDUsage_KeyboardW] = KeyW; + dialect_[kHIDUsage_KeyboardX] = KeyX; + dialect_[kHIDUsage_KeyboardY] = KeyY; + dialect_[kHIDUsage_KeyboardZ] = KeyZ; + + dialect_[kHIDUsage_KeyboardOpenBracket] = KeyBracketLeft; + dialect_[kHIDUsage_KeyboardBackslash] = KeyBackslash; + dialect_[kHIDUsage_KeyboardCloseBracket] = KeyBracketRight; + + dialect_[kHIDUsage_KeyboardGraveAccentAndTilde] = KeyGrave; + + dialect_[kHIDUsage_KeyboardLeftArrow] = KeyLeft; + dialect_[kHIDUsage_KeyboardRightArrow] = KeyRight; + dialect_[kHIDUsage_KeyboardUpArrow] = KeyUp; + dialect_[kHIDUsage_KeyboardDownArrow] = KeyDown; + dialect_[kHIDUsage_KeyboardInsert] = KeyInsert; + dialect_[kHIDUsage_KeyboardHome] = KeyHome; + dialect_[kHIDUsage_KeyboardDeleteForward] = KeyDelete; + dialect_[kHIDUsage_KeyboardEnd] = KeyEnd; + dialect_[kHIDUsage_KeyboardPageUp] = KeyPageUp; + dialect_[kHIDUsage_KeyboardPageDown] = KeyPageDown; + + dialect_[kHIDUsage_KeypadNumLock] = KeyNumLock; + dialect_[kHIDUsage_KeypadEqualSign] = KeyKpEqual; + dialect_[kHIDUsage_KeypadSlash] = KeyKpDivide; + dialect_[kHIDUsage_KeypadAsterisk] = KeyKpMultiply; + dialect_[kHIDUsage_KeypadHyphen] = KeyKpSubtract; + dialect_[kHIDUsage_KeypadPlus] = KeyKpAdd; + dialect_[kHIDUsage_KeypadEnter] = KeyKpEnter; + dialect_[kHIDUsage_Keypad0] = KeyKpInsert; + dialect_[kHIDUsage_Keypad1] = KeyKpEnd; + dialect_[kHIDUsage_Keypad2] = KeyKpDown; + dialect_[kHIDUsage_Keypad3] = KeyKpPageDown; + dialect_[kHIDUsage_Keypad4] = KeyKpLeft; + dialect_[kHIDUsage_Keypad5] = KeyKpBegin; + dialect_[kHIDUsage_Keypad6] = KeyKpRight; + dialect_[kHIDUsage_Keypad7] = KeyKpHome; + dialect_[kHIDUsage_Keypad8] = KeyKpUp; + dialect_[kHIDUsage_Keypad9] = KeyKpPageUp; + dialect_[kHIDUsage_KeypadPeriod] = KeyKpDelete; + + dialect_[kHIDUsage_KeyboardDeleteOrBackspace] = KeyBackSpace; + dialect_[kHIDUsage_KeyboardTab] = KeyTab; + dialect_[kHIDUsage_KeyboardReturnOrEnter] = KeyReturn; + dialect_[kHIDUsage_KeyboardCapsLock] = KeyCapsLock; + dialect_[kHIDUsage_KeyboardLeftShift] = KeyShiftL; + dialect_[kHIDUsage_KeyboardLeftControl] = KeyCtrlL; + dialect_[kHIDUsage_KeyboardLeftGUI] = KeySuperL; + dialect_[kHIDUsage_KeyboardLeftAlt] = KeyAltL; + dialect_[kHIDUsage_KeyboardRightAlt] = KeyAltR; + dialect_[kHIDUsage_KeyboardRightGUI] = KeySuperR; + dialect_[kHIDUsage_KeyboardRightControl] = KeyCtrlR; + dialect_[kHIDUsage_KeyboardRightShift] = KeyShiftR; + + dialect_[kHIDUsage_KeyboardNonUSPound] = KeyNumbersign; +} + +InputDeviceKeyboardImplMac::~InputDeviceKeyboardImplMac() +{ + IOHIDManagerRef ioManager = reinterpret_cast(ioManager_); + IOHIDManagerUnscheduleFromRunLoop(ioManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerClose(ioManager, 0); + CFRelease(ioManager); +} + +} + +#endif diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.h new file mode 100644 index 00000000..c25c9d67 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardMac.h @@ -0,0 +1,58 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDMAC_H_ +#define GAINPUTINPUTDEVICEKEYBOARDMAC_H_ + +#include "GainputInputDeviceKeyboardImpl.h" + +namespace gainput +{ + +class InputDeviceKeyboardImplMac : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplMac(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState); + ~InputDeviceKeyboardImplMac(); + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_RAW; + } + + InputDevice::DeviceState GetState() const { return deviceState_; } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + bool IsTextInputEnabled() const { return textInputEnabled_; } + void SetTextInputEnabled(bool enabled) { textInputEnabled_ = enabled; } + + char GetNextCharacter() + { + if (!textBuffer_.CanGet()) + { + return 0; + } + return textBuffer_.Get(); + } + + InputManager& manager_; + InputDevice& device_; + InputDevice::DeviceState deviceState_; + bool textInputEnabled_; + RingBuffer textBuffer_; + HashMap dialect_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; + +private: + void* ioManager_; +}; + +} + +#endif diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardNull.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardNull.h new file mode 100644 index 00000000..0b7448b6 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardNull.h @@ -0,0 +1,34 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDNULL_H_ +#define GAINPUTINPUTDEVICEKEYBOARDNULL_H_ + +#include "GainputInputDeviceKeyboardImpl.h" + +namespace gainput +{ + +class InputDeviceKeyboardImplNull : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplNull(InputManager& /*manager*/, DeviceId /*device*/) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_NULL; + } + + void Update(InputDeltaState* /*delta*/) + { + } + + bool IsTextInputEnabled() const { return false; } + void SetTextInputEnabled(bool /*enabled*/) { } + char GetNextCharacter() { return 0; } +}; + + +} + +#endif diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardWin.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardWin.h new file mode 100644 index 00000000..8732c002 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardWin.h @@ -0,0 +1,277 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDWIN_H_ +#define GAINPUTINPUTDEVICEKEYBOARDWIN_H_ + +#include "../GainputWindows.h" + +#include "GainputInputDeviceKeyboardImpl.h" +#include + +namespace gainput +{ + +class InputDeviceKeyboardImplWin : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplWin(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + textInputEnabled_(true), + dialect_(manager_.GetAllocator()), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), KeyCount_), + delta_(0) + { + dialect_[VK_ESCAPE] = KeyEscape; + dialect_[VK_F1] = KeyF1; + dialect_[VK_F2] = KeyF2; + dialect_[VK_F3] = KeyF3; + dialect_[VK_F4] = KeyF4; + dialect_[VK_F5] = KeyF5; + dialect_[VK_F6] = KeyF6; + dialect_[VK_F7] = KeyF7; + dialect_[VK_F8] = KeyF8; + dialect_[VK_F9] = KeyF9; + dialect_[VK_F10] = KeyF10; + dialect_[VK_F11] = KeyF11; + dialect_[VK_F12] = KeyF12; + dialect_[VK_PRINT] = KeyPrint; + dialect_[VK_SCROLL] = KeyScrollLock; + dialect_[VK_PAUSE] = KeyBreak; + + dialect_[VK_SPACE] = KeySpace; + + dialect_[VK_OEM_5] = KeyApostrophe; + dialect_[VK_OEM_COMMA] = KeyComma; + + dialect_['0'] = Key0; + dialect_['1'] = Key1; + dialect_['2'] = Key2; + dialect_['3'] = Key3; + dialect_['4'] = Key4; + dialect_['5'] = Key5; + dialect_['6'] = Key6; + dialect_['7'] = Key7; + dialect_['8'] = Key8; + dialect_['9'] = Key9; + + dialect_['A'] = KeyA; + dialect_['B'] = KeyB; + dialect_['C'] = KeyC; + dialect_['D'] = KeyD; + dialect_['E'] = KeyE; + dialect_['F'] = KeyF; + dialect_['G'] = KeyG; + dialect_['H'] = KeyH; + dialect_['I'] = KeyI; + dialect_['J'] = KeyJ; + dialect_['K'] = KeyK; + dialect_['L'] = KeyL; + dialect_['M'] = KeyM; + dialect_['N'] = KeyN; + dialect_['O'] = KeyO; + dialect_['P'] = KeyP; + dialect_['Q'] = KeyQ; + dialect_['R'] = KeyR; + dialect_['S'] = KeyS; + dialect_['T'] = KeyT; + dialect_['U'] = KeyU; + dialect_['V'] = KeyV; + dialect_['W'] = KeyW; + dialect_['X'] = KeyX; + dialect_['Y'] = KeyY; + dialect_['Z'] = KeyZ; + + dialect_[VK_LEFT] = KeyLeft; + dialect_[VK_RIGHT] = KeyRight; + dialect_[VK_UP] = KeyUp; + dialect_[VK_DOWN] = KeyDown; + dialect_[VK_INSERT] = KeyInsert; + dialect_[VK_HOME] = KeyHome; + dialect_[VK_DELETE] = KeyDelete; + dialect_[VK_END] = KeyEnd; + dialect_[VK_PRIOR] = KeyPageUp; + dialect_[VK_NEXT] = KeyPageDown; + + dialect_[VK_BACK] = KeyBackSpace; + dialect_[VK_TAB] = KeyTab; + dialect_[VK_RETURN] = KeyReturn; + dialect_[VK_CAPITAL] = KeyCapsLock; + dialect_[VK_LSHIFT] = KeyShiftL; + dialect_[VK_LCONTROL] = KeyCtrlL; + dialect_[VK_LWIN] = KeySuperL; + dialect_[VK_LMENU] = KeyAltL; + dialect_[VK_RMENU] = KeyAltR; + dialect_[VK_RWIN] = KeySuperR; + dialect_[VK_APPS] = KeyMenu; + dialect_[VK_RCONTROL] = KeyCtrlR; + dialect_[VK_RSHIFT] = KeyShiftR; + + dialect_[VK_VOLUME_MUTE] = KeyMute; + dialect_[VK_VOLUME_DOWN] = KeyVolumeDown; + dialect_[VK_VOLUME_UP] = KeyVolumeUp; + dialect_[VK_SNAPSHOT] = KeyPrint; + dialect_[VK_OEM_4] = KeyExtra1; + dialect_[VK_OEM_6] = KeyExtra2; + dialect_[VK_BROWSER_BACK] = KeyBack; + dialect_[VK_BROWSER_FORWARD] = KeyForward; + dialect_[VK_OEM_MINUS] = KeyMinus; + dialect_[VK_OEM_PERIOD] = KeyPeriod; + dialect_[VK_OEM_2] = KeyExtra3; + dialect_[VK_OEM_PLUS] = KeyPlus; + dialect_[VK_OEM_7] = KeyExtra4; + dialect_[VK_OEM_3] = KeyExtra5; + dialect_[VK_OEM_1] = KeyExtra6; + + dialect_[0xff] = KeyFn; // Marked as "reserved". + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + bool IsTextInputEnabled() const { return textInputEnabled_; } + void SetTextInputEnabled(bool enabled) { textInputEnabled_ = enabled; } + + char GetNextCharacter() + { + if (!textBuffer_.CanGet()) + { + return 0; + } + return textBuffer_.Get(); + } + + void HandleMessage(const MSG& msg) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + const unsigned LParamExtendedKeymask = 1 << 24; + + if (msg.message == WM_CHAR) + { + if (!textInputEnabled_) + { + return; + } + + const int key = msg.wParam; + if (key == 0x08 // backspace + || key == 0x0A // linefeed + || key == 0x1B // escape + || key == 0x09 // tab + || key == 0x0D // carriage return + || key > 255) + { + return; + } + const char charKey = key; + textBuffer_.Put(charKey); +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("Text: %c\n", charKey); +#endif + return; + } + + bool pressed = false; + unsigned winKey; + switch (msg.message) + { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + pressed = true; + winKey = msg.wParam; + break; + case WM_KEYUP: + case WM_SYSKEYUP: + pressed = false; + winKey = msg.wParam; + break; + default: // Non-keyboard message + return; + } + + if (winKey == VK_CONTROL) + { + winKey = (msg.lParam & LParamExtendedKeymask) ? VK_RCONTROL : VK_LCONTROL; + } + else if (winKey == VK_MENU) + { + winKey = (msg.lParam & LParamExtendedKeymask) ? VK_RMENU : VK_LMENU; + } + else if (winKey == VK_SHIFT) + { + if (pressed) + { + if (GetKeyState(VK_LSHIFT) & 0x8000) + { + winKey = VK_LSHIFT; + } + else if (GetKeyState(VK_RSHIFT) & 0x8000) + { + winKey = VK_RSHIFT; + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Not sure which shift this is.\n"); + } +#endif + } + else + { + if (previousState_->GetBool(KeyShiftL) && !(GetKeyState(VK_LSHIFT) & 0x8000)) + { + winKey = VK_LSHIFT; + } + else if (previousState_->GetBool(KeyShiftR) && !(GetKeyState(VK_RSHIFT) & 0x8000)) + { + winKey = VK_RSHIFT; + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Not sure which shift this is.\n"); + } +#endif + } + } + // TODO handle l/r alt properly + +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("Keyboard: %d, %i\n", winKey, pressed); +#endif + + if (dialect_.count(winKey)) + { + const DeviceButtonId buttonId = dialect_[winKey]; + HandleButton(device_, nextState_, delta_, buttonId, pressed); + } + } + +private: + InputManager& manager_; + InputDevice& device_; + bool textInputEnabled_; + RingBuffer textBuffer_; + HashMap dialect_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; +}; + + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputInputDeviceKeyboardWinRaw.h b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardWinRaw.h new file mode 100644 index 00000000..e1644192 --- /dev/null +++ b/lib/source/gainput/keyboard/GainputInputDeviceKeyboardWinRaw.h @@ -0,0 +1,259 @@ + +#ifndef GAINPUTINPUTDEVICEKEYBOARDWINRAW_H_ +#define GAINPUTINPUTDEVICEKEYBOARDWINRAW_H_ + +#include "../GainputWindows.h" + +#include "GainputInputDeviceKeyboardImpl.h" +#include + +#ifndef HID_USAGE_PAGE_GENERIC +#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) +#endif +#ifndef HID_USAGE_GENERIC_KEYBOARD +#define HID_USAGE_GENERIC_KEYBOARD ((USHORT) 0x06) +#endif + +namespace gainput +{ + +class InputDeviceKeyboardImplWinRaw : public InputDeviceKeyboardImpl +{ +public: + InputDeviceKeyboardImplWinRaw(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + deviceState_(InputDevice::DS_UNAVAILABLE), + dialect_(manager_.GetAllocator()), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), KeyCount_), + delta_(0) + { + RAWINPUTDEVICE Rid[1]; + Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; + Rid[0].usUsage = HID_USAGE_GENERIC_KEYBOARD; + Rid[0].dwFlags = 0;//RIDEV_NOLEGACY; + Rid[0].hwndTarget = 0; + if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]))) + { + deviceState_ = InputDevice::DS_OK; + } + + dialect_[VK_ESCAPE] = KeyEscape; + dialect_[VK_F1] = KeyF1; + dialect_[VK_F2] = KeyF2; + dialect_[VK_F3] = KeyF3; + dialect_[VK_F4] = KeyF4; + dialect_[VK_F5] = KeyF5; + dialect_[VK_F6] = KeyF6; + dialect_[VK_F7] = KeyF7; + dialect_[VK_F8] = KeyF8; + dialect_[VK_F9] = KeyF9; + dialect_[VK_F10] = KeyF10; + dialect_[VK_F11] = KeyF11; + dialect_[VK_F12] = KeyF12; + dialect_[VK_PRINT] = KeyPrint; + dialect_[VK_SCROLL] = KeyScrollLock; + dialect_[VK_PAUSE] = KeyBreak; + + dialect_[VK_SPACE] = KeySpace; + + dialect_[VK_OEM_5] = KeyApostrophe; + dialect_[VK_OEM_COMMA] = KeyComma; + + dialect_['0'] = Key0; + dialect_['1'] = Key1; + dialect_['2'] = Key2; + dialect_['3'] = Key3; + dialect_['4'] = Key4; + dialect_['5'] = Key5; + dialect_['6'] = Key6; + dialect_['7'] = Key7; + dialect_['8'] = Key8; + dialect_['9'] = Key9; + + dialect_['A'] = KeyA; + dialect_['B'] = KeyB; + dialect_['C'] = KeyC; + dialect_['D'] = KeyD; + dialect_['E'] = KeyE; + dialect_['F'] = KeyF; + dialect_['G'] = KeyG; + dialect_['H'] = KeyH; + dialect_['I'] = KeyI; + dialect_['J'] = KeyJ; + dialect_['K'] = KeyK; + dialect_['L'] = KeyL; + dialect_['M'] = KeyM; + dialect_['N'] = KeyN; + dialect_['O'] = KeyO; + dialect_['P'] = KeyP; + dialect_['Q'] = KeyQ; + dialect_['R'] = KeyR; + dialect_['S'] = KeyS; + dialect_['T'] = KeyT; + dialect_['U'] = KeyU; + dialect_['V'] = KeyV; + dialect_['W'] = KeyW; + dialect_['X'] = KeyX; + dialect_['Y'] = KeyY; + dialect_['Z'] = KeyZ; + + dialect_[VK_LEFT] = KeyLeft; + dialect_[VK_RIGHT] = KeyRight; + dialect_[VK_UP] = KeyUp; + dialect_[VK_DOWN] = KeyDown; + dialect_[VK_INSERT] = KeyInsert; + dialect_[VK_HOME] = KeyHome; + dialect_[VK_DELETE] = KeyDelete; + dialect_[VK_END] = KeyEnd; + dialect_[VK_PRIOR] = KeyPageUp; + dialect_[VK_NEXT] = KeyPageDown; + + dialect_[VK_BACK] = KeyBackSpace; + dialect_[VK_TAB] = KeyTab; + dialect_[VK_RETURN] = KeyReturn; + dialect_[VK_CAPITAL] = KeyCapsLock; + dialect_[VK_LSHIFT] = KeyShiftL; + dialect_[VK_LCONTROL] = KeyCtrlL; + dialect_[VK_LWIN] = KeySuperL; + dialect_[VK_LMENU] = KeyAltL; + dialect_[VK_RMENU] = KeyAltR; + dialect_[VK_RWIN] = KeySuperR; + dialect_[VK_APPS] = KeyMenu; + dialect_[VK_RCONTROL] = KeyCtrlR; + dialect_[VK_RSHIFT] = KeyShiftR; + + dialect_[VK_VOLUME_MUTE] = KeyMute; + dialect_[VK_VOLUME_DOWN] = KeyVolumeDown; + dialect_[VK_VOLUME_UP] = KeyVolumeUp; + dialect_[VK_SNAPSHOT] = KeyPrint; + dialect_[VK_OEM_4] = KeyExtra1; + dialect_[VK_OEM_6] = KeyExtra2; + dialect_[VK_BROWSER_BACK] = KeyBack; + dialect_[VK_BROWSER_FORWARD] = KeyForward; + dialect_[VK_OEM_MINUS] = KeyMinus; + dialect_[VK_OEM_PERIOD] = KeyPeriod; + dialect_[VK_OEM_2] = KeyExtra3; + dialect_[VK_OEM_PLUS] = KeyPlus; + dialect_[VK_OEM_7] = KeyExtra4; + dialect_[VK_OEM_3] = KeyExtra5; + dialect_[VK_OEM_1] = KeyExtra6; + + dialect_[VK_NUMLOCK] = KeyNumLock; + dialect_[VK_NUMPAD0] = KeyKpInsert; + dialect_[VK_NUMPAD1] = KeyKpEnd; + dialect_[VK_NUMPAD2] = KeyKpDown; + dialect_[VK_NUMPAD3] = KeyKpPageDown; + dialect_[VK_NUMPAD4] = KeyKpLeft; + dialect_[VK_NUMPAD5] = KeyKpBegin; + dialect_[VK_NUMPAD6] = KeyKpRight; + dialect_[VK_NUMPAD7] = KeyKpHome; + dialect_[VK_NUMPAD8] = KeyKpUp; + dialect_[VK_NUMPAD9] = KeyKpPageUp; + dialect_[VK_DECIMAL] = KeyKpDelete; + dialect_[VK_DIVIDE] = KeyKpDivide; + dialect_[VK_MULTIPLY] = KeyKpMultiply; + dialect_[VK_SUBTRACT] = KeyKpSubtract; + dialect_[VK_ADD] = KeyKpAdd; + + dialect_[0xff] = KeyFn; // Marked as "reserved". + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_RAW; + } + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + bool IsTextInputEnabled() const { return false; } + void SetTextInputEnabled(bool enabled) { } + char GetNextCharacter() { return 0; } + + void HandleMessage(const MSG& msg) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + if (msg.message != WM_INPUT) + { + return; + } + + UINT dwSize = 40; + static BYTE lpb[40]; + + GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)); + RAWINPUT* raw = (RAWINPUT*)lpb; + + if (raw->header.dwType == RIM_TYPEKEYBOARD) + { + USHORT vkey = raw->data.keyboard.VKey; + + if (vkey == VK_CONTROL) + { + if (raw->data.keyboard.Flags & RI_KEY_E0) + vkey = VK_RCONTROL; + else + vkey = VK_LCONTROL; + } + else if (vkey == VK_SHIFT) + { + if (raw->data.keyboard.MakeCode == 0x36) + vkey = VK_RSHIFT; + else + vkey = VK_LSHIFT; + } + else if (vkey == VK_MENU) + { + if (raw->data.keyboard.Flags & RI_KEY_E0) + vkey = VK_RMENU; + else + vkey = VK_LMENU; + } + + if (dialect_.count(vkey)) + { + const DeviceButtonId buttonId = dialect_[vkey]; +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("%d mapped to: %d\n", vkey, buttonId); +#endif + const bool pressed = raw->data.keyboard.Message == WM_KEYDOWN || raw->data.keyboard.Message == WM_SYSKEYDOWN; + HandleButton(device_, nextState_, delta_, buttonId, pressed); + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Unmapped raw vkey: %d\n", vkey); + } +#endif + } + } + +private: + InputManager& manager_; + InputDevice& device_; + InputDevice::DeviceState deviceState_; + HashMap dialect_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; +}; + + +} + +#endif + diff --git a/lib/source/gainput/keyboard/GainputKeyboardKeyNames.h b/lib/source/gainput/keyboard/GainputKeyboardKeyNames.h new file mode 100644 index 00000000..9277d03e --- /dev/null +++ b/lib/source/gainput/keyboard/GainputKeyboardKeyNames.h @@ -0,0 +1,184 @@ + +#ifndef GAINPUTKEYBOARDKEYNAMES_H_ +#define GAINPUTKEYBOARDKEYNAMES_H_ + +namespace gainput +{ + +inline +void +GetKeyboardKeyNames(HashMap& names) +{ + names[KeyEscape] = "escape"; + names[KeyF1] = "f1"; + names[KeyF2] = "f2"; + names[KeyF3] = "f3"; + names[KeyF4] = "f4"; + names[KeyF5] = "f5"; + names[KeyF6] = "f6"; + names[KeyF7] = "f7"; + names[KeyF8] = "f8"; + names[KeyF9] = "f9"; + names[KeyF10] = "f10"; + names[KeyF11] = "f11"; + names[KeyF12] = "f12"; + names[KeyPrint] = "print"; + names[KeyScrollLock] = "scroll_lock"; + names[KeyBreak] = "break"; + names[KeySpace] = "space"; + names[KeyApostrophe] = "apostrophe"; + names[KeyComma] = "comma"; + names[KeyMinus] = "minus"; + names[KeyPeriod] = "period"; + names[KeySlash] = "slash"; + names[Key0] = "0"; + names[Key1] = "1"; + names[Key2] = "2"; + names[Key3] = "3"; + names[Key4] = "4"; + names[Key5] = "5"; + names[Key6] = "6"; + names[Key7] = "7"; + names[Key8] = "8"; + names[Key9] = "9"; + names[KeySemicolon] = "semicolon"; + names[KeyLess] = "less"; + names[KeyEqual] = "equal"; + names[KeyA] = "a"; + names[KeyB] = "b"; + names[KeyC] = "c"; + names[KeyD] = "d"; + names[KeyE] = "e"; + names[KeyF] = "f"; + names[KeyG] = "g"; + names[KeyH] = "h"; + names[KeyI] = "i"; + names[KeyJ] = "j"; + names[KeyK] = "k"; + names[KeyL] = "l"; + names[KeyM] = "m"; + names[KeyN] = "n"; + names[KeyO] = "o"; + names[KeyP] = "p"; + names[KeyQ] = "q"; + names[KeyR] = "r"; + names[KeyS] = "s"; + names[KeyT] = "t"; + names[KeyU] = "u"; + names[KeyV] = "v"; + names[KeyW] = "w"; + names[KeyX] = "x"; + names[KeyY] = "y"; + names[KeyZ] = "z"; + names[KeyBracketLeft] = "bracket_left"; + names[KeyBackslash] = "backslash"; + names[KeyBracketRight] = "bracket_right"; + names[KeyGrave] = "grave"; + names[KeyLeft] = "left"; + names[KeyRight] = "right"; + names[KeyUp] = "up"; + names[KeyDown] = "down"; + names[KeyInsert] = "insert"; + names[KeyHome] = "home"; + names[KeyDelete] = "delete"; + names[KeyEnd] = "end"; + names[KeyPageUp] = "page_up"; + names[KeyPageDown] = "page_down"; + names[KeyNumLock] = "num_lock"; + names[KeyKpDivide] = "kp_divide"; + names[KeyKpMultiply] = "kp_multiply"; + names[KeyKpSubtract] = "kp_subtract"; + names[KeyKpAdd] = "kp_add"; + names[KeyKpEnter] = "kp_enter"; + names[KeyKpInsert] = "kp_insert"; + names[KeyKpEnd] = "kp_end"; + names[KeyKpDown] = "kp_down"; + names[KeyKpPageDown] = "kp_page_down"; + names[KeyKpLeft] = "kp_left"; + names[KeyKpBegin] = "kp_begin"; + names[KeyKpRight] = "kp_right"; + names[KeyKpHome] = "kp_home"; + names[KeyKpUp] = "kp_up"; + names[KeyKpPageUp] = "kp_page_up"; + names[KeyKpDelete] = "kp_delete"; + names[KeyBackSpace] = "back_space"; + names[KeyTab] = "tab"; + names[KeyReturn] = "return"; + names[KeyCapsLock] = "caps_lock"; + names[KeyShiftL] = "shift_l"; + names[KeyCtrlL] = "ctrl_l"; + names[KeySuperL] = "super_l"; + names[KeyAltL] = "alt_l"; + names[KeyAltR] = "alt_r"; + names[KeySuperR] = "super_r"; + names[KeyMenu] = "menu"; + names[KeyCtrlR] = "ctrl_r"; + names[KeyShiftR] = "shift_r"; + names[KeyBack] = "back"; + names[KeySoftLeft] = "soft_left"; + names[KeySoftRight] = "soft_right"; + names[KeyCall] = "call"; + names[KeyEndcall] = "endcall"; + names[KeyStar] = "star"; + names[KeyPound] = "pound"; + names[KeyDpadCenter] = "dpad_center"; + names[KeyVolumeUp] = "volume_up"; + names[KeyVolumeDown] = "volume_down"; + names[KeyPower] = "power"; + names[KeyCamera] = "camera"; + names[KeyClear] = "clear"; + names[KeySymbol] = "symbol"; + names[KeyExplorer] = "explorer"; + names[KeyEnvelope] = "envelope"; + names[KeyEquals] = "equals"; + names[KeyAt] = "at"; + names[KeyHeadsethook] = "headsethook"; + names[KeyFocus] = "focus"; + names[KeyPlus] = "plus"; + names[KeyNotification] = "notification"; + names[KeySearch] = "search"; + names[KeyMediaPlayPause] = "media_play_pause"; + names[KeyMediaStop] = "media_stop"; + names[KeyMediaNext] = "media_next"; + names[KeyMediaPrevious] = "media_previous"; + names[KeyMediaRewind] = "media_rewind"; + names[KeyMediaFastForward] = "media_fast_forward"; + names[KeyMute] = "mute"; + names[KeyPictsymbols] = "pictsymbols"; + names[KeySwitchCharset] = "switch_charset"; + names[KeyForward] = "forward"; + names[KeyExtra1] = "extra_1"; + names[KeyExtra2] = "extra_2"; + names[KeyExtra3] = "extra_3"; + names[KeyExtra4] = "extra_4"; + names[KeyExtra5] = "extra_5"; + names[KeyExtra6] = "extra_6"; + names[KeyFn] = "fn"; + names[KeyCircumflex] = "circumflex"; + names[KeySsharp] = "ssharp"; + names[KeyAcute] = "acute"; + names[KeyAltGr] = "alt_gr"; + names[KeyNumbersign] = "numbersign"; + names[KeyUdiaeresis] = "udiaeresis"; + names[KeyAdiaeresis] = "adiaeresis"; + names[KeyOdiaeresis] = "odiaeresis"; + names[KeySection] = "section"; + names[KeyAring] = "aring"; + names[KeyDiaeresis] = "diaeresis"; + names[KeyTwosuperior] = "twosuperior"; + names[KeyRightParenthesis] = "right_parenthesis"; + names[KeyDollar] = "dollar"; + names[KeyUgrave] = "ugrave"; + names[KeyAsterisk] = "asterisk"; + names[KeyColon] = "colon"; + names[KeyExclam] = "exclam"; + names[KeyBraceLeft] = "brace_left"; + names[KeyBraceRight] = "brace_right"; + names[KeySysRq] = "sys_rq"; +} + + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouse.cpp b/lib/source/gainput/mouse/GainputInputDeviceMouse.cpp new file mode 100644 index 00000000..20993d93 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouse.cpp @@ -0,0 +1,149 @@ + +#include +#include + +#include "GainputInputDeviceMouseImpl.h" +#include "GainputInputDeviceMouseNull.h" +#include "GainputMouseInfo.h" +#include +#include +#include + +#if defined(GAINPUT_PLATFORM_LINUX) + #include "GainputInputDeviceMouseLinux.h" + #include "GainputInputDeviceMouseEvdev.h" +#elif defined(GAINPUT_PLATFORM_WIN) + #include "GainputInputDeviceMouseWin.h" + #include "GainputInputDeviceMouseWinRaw.h" +#elif defined(GAINPUT_PLATFORM_MAC) + #include "GainputInputDeviceMouseMac.h" +#endif + +namespace gainput +{ + + +InputDeviceMouse::InputDeviceMouse(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant) : + InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_MOUSE) : index), + impl_(0) +{ + state_ = manager.GetAllocator().New(manager.GetAllocator(), MouseButtonCount + MouseAxisCount); + GAINPUT_ASSERT(state_); + previousState_ = manager.GetAllocator().New(manager.GetAllocator(), MouseButtonCount + MouseAxisCount); + GAINPUT_ASSERT(previousState_); + +#if defined(GAINPUT_PLATFORM_LINUX) + if (variant == DV_STANDARD) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } + else if (variant == DV_RAW) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } +#elif defined(GAINPUT_PLATFORM_WIN) + if (variant == DV_STANDARD) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } + else if (variant == DV_RAW) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } +#elif defined(GAINPUT_PLATFORM_MAC) + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); +#endif + + if (!impl_) + { + impl_ = manager.GetAllocator().New(manager, device); + } + GAINPUT_ASSERT(impl_); +} + +InputDeviceMouse::~InputDeviceMouse() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); + manager_.GetAllocator().Delete(impl_); +} + +void +InputDeviceMouse::InternalUpdate(InputDeltaState* delta) +{ + impl_->Update(delta); + + if ((manager_.IsDebugRenderingEnabled() || IsDebugRenderingEnabled()) + && manager_.GetDebugRenderer()) + { + DebugRenderer* debugRenderer = manager_.GetDebugRenderer(); + InputState* state = GetInputState(); + const float x = state->GetFloat(MouseAxisX); + const float y = state->GetFloat(MouseAxisY); + debugRenderer->DrawCircle(x, y, 0.01f); + + for (int i = 0; i < MouseButtonCount; ++i) + { + if (state->GetBool(MouseButton0 + i)) + { + debugRenderer->DrawCircle(x, y, 0.03f + (0.005f * float(i+1))); + } + } + } +} + +InputDevice::DeviceState +InputDeviceMouse::InternalGetState() const +{ + return impl_->GetState(); +} + +InputDevice::DeviceVariant +InputDeviceMouse::GetVariant() const +{ + return impl_->GetVariant(); +} + +size_t +InputDeviceMouse::GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + GAINPUT_ASSERT(outButtons); + GAINPUT_ASSERT(maxButtonCount > 0); + return CheckAllButtonsDown(outButtons, maxButtonCount, MouseButton0, MouseButtonCount_); +} + +size_t +InputDeviceMouse::GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + GAINPUT_ASSERT(buffer); + GAINPUT_ASSERT(bufferLength > 0); + strncpy(buffer, deviceButtonInfos[deviceButton].name, bufferLength); + buffer[bufferLength-1] = 0; + const size_t nameLen = strlen(deviceButtonInfos[deviceButton].name); + return nameLen >= bufferLength ? bufferLength : nameLen+1; +} + +ButtonType +InputDeviceMouse::GetButtonType(DeviceButtonId deviceButton) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + return deviceButtonInfos[deviceButton].type; +} + +DeviceButtonId +InputDeviceMouse::GetButtonByName(const char* name) const +{ + GAINPUT_ASSERT(name); + for (unsigned i = 0; i < MouseButtonCount + MouseAxisCount; ++i) + { + if (strcmp(name, deviceButtonInfos[i].name) == 0) + { + return DeviceButtonId(i); + } + } + return InvalidDeviceButtonId; +} + +} + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseEvdev.h b/lib/source/gainput/mouse/GainputInputDeviceMouseEvdev.h new file mode 100644 index 00000000..0c01931b --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseEvdev.h @@ -0,0 +1,161 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSEEVDEV_H_ +#define GAINPUTINPUTDEVICEMOUSEEVDEV_H_ + +#include "../GainputHelpersEvdev.h" + +namespace gainput +{ + +class InputDeviceMouseImplEvdev : public InputDeviceMouseImpl +{ +public: + InputDeviceMouseImplEvdev(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(state), + previousState_(previousState), + fd_(-1), + buttonsToReset_(manager.GetAllocator()) + { + unsigned matchingDeviceCount = 0; + for (unsigned i = 0; i < EvdevDeviceCount; ++i) + { + fd_ = open(EvdevDeviceIds[i], O_RDONLY|O_NONBLOCK); + if (fd_ == -1) + { + continue; + } + + EvdevDevice evdev(fd_); + + if (evdev.IsValid()) + { + if (evdev.GetDeviceType() == InputDevice::DT_MOUSE) + { + if (matchingDeviceCount == manager_.GetDeviceCountByType(InputDevice::DT_MOUSE)) + { + break; + } + ++matchingDeviceCount; + } + } + + close(fd_); + fd_ = -1; + } + } + + ~InputDeviceMouseImplEvdev() + { + if (fd_ != -1) + { + close(fd_); + } + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_RAW; + } + + InputDevice::DeviceState GetState() const + { + return fd_ != -1 ? InputDevice::DS_OK : InputDevice::DS_UNAVAILABLE; + } + + void Update(InputDeltaState* delta) + { + for (Array::const_iterator it = buttonsToReset_.begin(); + it != buttonsToReset_.end(); + ++it) + { + HandleButton(device_, state_, delta, *it, false); + } + buttonsToReset_.clear(); + + if (fd_ < 0) + { + return; + } + + struct input_event event; + + for (;;) + { + int len = read(fd_, &event, sizeof(struct input_event)); + if (len != sizeof(struct input_event)) + { + break; + } + + if (event.type == EV_KEY) + { + int button = -1; + if (event.code == BTN_LEFT) + button = MouseButtonLeft; + else if (event.code == BTN_MIDDLE) + button = MouseButtonMiddle; + else if (event.code == BTN_RIGHT) + button = MouseButtonRight; + else if (event.code == BTN_SIDE) + button = MouseButton5; + else if (event.code == BTN_EXTRA) + button = MouseButton6; + + if (button != -1) + { + HandleButton(device_, state_, delta, DeviceButtonId(button), bool(event.value)); + } + } + else if (event.type == EV_REL) + { + int button = -1; + if (event.code == REL_X) + button = MouseAxisX; + else if (event.code == REL_Y) + button = MouseAxisY; + else if (event.code == REL_HWHEEL) + button = MouseButton7; + else if (event.code == REL_WHEEL) + button = MouseButtonWheelUp; + + if (button == MouseButtonWheelUp) + { + if (event.value < 0) + button = MouseButtonWheelDown; + HandleButton(device_, state_, delta, DeviceButtonId(button), true); + buttonsToReset_.push_back(button); + } + else if (button == MouseButton7) + { + if (event.value < 0) + button = MouseButton8; + HandleButton(device_, state_, delta, DeviceButtonId(button), true); + buttonsToReset_.push_back(button); + } + else if (button != -1) + { + const DeviceButtonId buttonId(button); + const float prevValue = previousState_.GetFloat(buttonId); + HandleAxis(device_, state_, delta, DeviceButtonId(button), prevValue + float(event.value)); + } + } + } + } + + + +private: + InputManager& manager_; + InputDevice& device_; + InputState& state_; + InputState& previousState_; + int fd_; + Array buttonsToReset_; +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseImpl.h b/lib/source/gainput/mouse/GainputInputDeviceMouseImpl.h new file mode 100644 index 00000000..15ee835a --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseImpl.h @@ -0,0 +1,20 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSEIMPL_H_ +#define GAINPUTINPUTDEVICEMOUSEIMPL_H_ + +namespace gainput +{ + +class InputDeviceMouseImpl +{ +public: + virtual ~InputDeviceMouseImpl() { } + virtual InputDevice::DeviceVariant GetVariant() const = 0; + virtual InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } + virtual void Update(InputDeltaState* delta) = 0; +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseLinux.h b/lib/source/gainput/mouse/GainputInputDeviceMouseLinux.h new file mode 100644 index 00000000..6265c5d1 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseLinux.h @@ -0,0 +1,122 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSELINUX_H_ +#define GAINPUTINPUTDEVICEMOUSELINUX_H_ + +#include + +#include "GainputInputDeviceMouseImpl.h" +#include + +namespace gainput +{ + +class InputDeviceMouseImplLinux : public InputDeviceMouseImpl +{ +public: + InputDeviceMouseImplLinux(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), MouseButtonCount + MouseAxisCount), + delta_(0) + { + const size_t size = sizeof(bool)*MouseButtonCount; + isWheel_ = static_cast(manager_.GetAllocator().Allocate(size)); + GAINPUT_ASSERT(isWheel_); + memset(isWheel_, 0, size); + pressedThisFrame_ = static_cast(manager_.GetAllocator().Allocate(size)); + GAINPUT_ASSERT(pressedThisFrame_); + } + + ~InputDeviceMouseImplLinux() + { + manager_.GetAllocator().Deallocate(isWheel_); + manager_.GetAllocator().Deallocate(pressedThisFrame_); + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + + // Reset mouse wheel buttons + for (unsigned i = 0; i < MouseButtonCount; ++i) + { + const DeviceButtonId buttonId = i; + const bool oldValue = previousState_->GetBool(buttonId); + if (isWheel_[i] && oldValue) + { + const bool pressed = false; + HandleButton(device_, nextState_, delta_, buttonId, pressed); + } + } + + *state_ = nextState_; + + memset(pressedThisFrame_, 0, sizeof(bool) * MouseButtonCount); + } + + void HandleEvent(XEvent& event) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + switch (event.type) + { + case MotionNotify: + { + const XMotionEvent& motionEvent = event.xmotion; + const float x = float(motionEvent.x)/float(manager_.GetDisplayWidth()); + const float y = float(motionEvent.y)/float(manager_.GetDisplayHeight()); + HandleAxis(device_, nextState_, delta_, MouseAxisX, x); + HandleAxis(device_, nextState_, delta_, MouseAxisY, y); + break; + } + case ButtonPress: + case ButtonRelease: + { + const XButtonEvent& buttonEvent = event.xbutton; + GAINPUT_ASSERT(buttonEvent.button > 0); + const DeviceButtonId buttonId = buttonEvent.button-1; + GAINPUT_ASSERT(buttonId <= MouseButtonMax); + const bool pressed = event.type == ButtonPress; + + if (!pressed && pressedThisFrame_[buttonId]) + { + // This is a mouse wheel button. Ignore release now, reset next frame. + isWheel_[buttonId] = true; + } + else if (buttonEvent.button < MouseButtonCount) + { + HandleButton(device_, nextState_, delta_, buttonId, pressed); + } + + if (pressed) + { + pressedThisFrame_[buttonId] = true; + } + break; + } + } + } + +private: + InputManager& manager_; + InputDevice& device_; + bool* isWheel_; + bool* pressedThisFrame_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseMac.h b/lib/source/gainput/mouse/GainputInputDeviceMouseMac.h new file mode 100644 index 00000000..70bdf509 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseMac.h @@ -0,0 +1,36 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSEMAC_H_ +#define GAINPUTINPUTDEVICEMOUSEMAC_H_ + +#include "GainputInputDeviceMouseImpl.h" + +namespace gainput +{ + +class InputDeviceMouseImplMac : public InputDeviceMouseImpl +{ +public: + InputDeviceMouseImplMac(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState); + ~InputDeviceMouseImplMac(); + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta); + + InputManager& manager_; + InputDevice& device_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; + + void* eventTap_; +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseMac.mm b/lib/source/gainput/mouse/GainputInputDeviceMouseMac.mm new file mode 100644 index 00000000..4074f5e7 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseMac.mm @@ -0,0 +1,166 @@ + +#include + +#ifdef GAINPUT_PLATFORM_MAC + +#include "GainputInputDeviceMouseMac.h" + +#include +#include +#include + +#import +#import + +namespace { + +static float theoreticalTitleBarHeight() +{ + NSRect frame = NSMakeRect (0, 0, 100, 100); + NSRect contentRect = [NSWindow contentRectForFrameRect:frame + styleMask:NSTitledWindowMask]; + return (frame.size.height - contentRect.size.height); +} + +CGEventRef MouseTap(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void* user) +{ + gainput::InputDeviceMouseImplMac* device = reinterpret_cast(user); + GAINPUT_ASSERT(device->previousState_); + + if (type == kCGEventTapDisabledByTimeout + || type == kCGEventTapDisabledByUserInput) + { + // Probably timeout, re-enable! + CGEventTapEnable(reinterpret_cast(device->eventTap_), true); + } + + gainput::InputManager& manager = device->manager_; + NSApplication* app = [NSApplication sharedApplication]; + NSWindow* window = app.keyWindow; + if (window) + { + CGPoint theLocation = CGEventGetUnflippedLocation(event); + + NSRect rect = NSMakeRect(theLocation.x, theLocation.y, 0, 0); + NSRect rect2 = [window convertRectFromScreen:rect]; + + float titleBarHeight = (window.styleMask & NSFullScreenWindowMask) ? 0.0f : theoreticalTitleBarHeight(); + const float x = rect2.origin.x / window.frame.size.width; + const float y = 1.0f - (rect2.origin.y / (window.frame.size.height - titleBarHeight)); + + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, gainput::MouseAxisX, x); + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, gainput::MouseAxisY, y); + + if (type == kCGEventLeftMouseDown || type == kCGEventLeftMouseUp) + { + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, gainput::MouseButton0, type == kCGEventLeftMouseDown); + } + else if (type == kCGEventRightMouseDown || type == kCGEventRightMouseUp) + { + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, gainput::MouseButton2, type == kCGEventRightMouseDown); + } + else if (type == kCGEventOtherMouseDown || type == kCGEventOtherMouseUp) + { + int buttonNum = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber); + gainput::DeviceButtonId buttonId = gainput::MouseButton1; + if (buttonNum > kCGMouseButtonCenter) + { + buttonId = gainput::MouseButton3 + buttonNum - kCGMouseButtonCenter; + } + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, buttonId, type == kCGEventOtherMouseDown); + } + else if (type == kCGEventScrollWheel) + { + int const deltaAxis = CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1); + if (deltaAxis != 0) + { + manager.EnqueueConcurrentChange( + device->device_, + device->nextState_, + device->delta_, + deltaAxis > 0 ? gainput::MouseButtonWheelDown : gainput::MouseButtonWheelUp, + true); + } + } + } + else + { + // Window was unfocused. + for (unsigned i = gainput::MouseButton0; i < gainput::MouseButtonCount; ++ i) + { + gainput::HandleButton(device->device_, device->nextState_, device->delta_, i, false); + } + } + + return event; +} + +} + +namespace gainput +{ + +InputDeviceMouseImplMac::InputDeviceMouseImplMac(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), MouseButtonCount + MouseAxisCount), + delta_(0) +{ + CGEventMask eventMask = + CGEventMaskBit(kCGEventLeftMouseDown) + | CGEventMaskBit(kCGEventLeftMouseUp) + | CGEventMaskBit(kCGEventRightMouseDown) + | CGEventMaskBit(kCGEventRightMouseUp) + | CGEventMaskBit(kCGEventOtherMouseDown) + | CGEventMaskBit(kCGEventOtherMouseUp) + | CGEventMaskBit(kCGEventMouseMoved) + | CGEventMaskBit(kCGEventLeftMouseDragged) + | CGEventMaskBit(kCGEventRightMouseDragged) + | CGEventMaskBit(kCGEventOtherMouseDragged) + | CGEventMaskBit(kCGEventScrollWheel) + ; + + CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, + kCGHeadInsertEventTap, + kCGEventTapOptionDefault, + eventMask, + MouseTap, + this); + + if (!eventTap) + { + GAINPUT_ASSERT(false); + return; + } + + eventTap_ = eventTap; + + CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes); + + CGEventTapEnable(eventTap, true); +} + +InputDeviceMouseImplMac::~InputDeviceMouseImplMac() +{ + CFRelease(reinterpret_cast(eventTap_)); +} + + +void InputDeviceMouseImplMac::Update(InputDeltaState* delta) +{ + delta_ = delta; + + // Reset mouse wheel buttons + HandleButton(device_, nextState_, delta_, MouseButtonWheelUp, false); + HandleButton(device_, nextState_, delta_, MouseButtonWheelDown, false); + + *state_ = nextState_; +} + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseNull.h b/lib/source/gainput/mouse/GainputInputDeviceMouseNull.h new file mode 100644 index 00000000..a5660216 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseNull.h @@ -0,0 +1,28 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSENULL_H_ +#define GAINPUTINPUTDEVICEMOUSENULL_H_ + +namespace gainput +{ + +class InputDeviceMouseImplNull : public InputDeviceMouseImpl +{ +public: + InputDeviceMouseImplNull(InputManager& /*manager*/, DeviceId /*device*/) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_NULL; + } + + void Update(InputDeltaState* /*delta*/) + { + } +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseWin.h b/lib/source/gainput/mouse/GainputInputDeviceMouseWin.h new file mode 100644 index 00000000..53e3efb4 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseWin.h @@ -0,0 +1,135 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSEWIN_H_ +#define GAINPUTINPUTDEVICEMOUSEWIN_H_ + +#include "GainputInputDeviceMouseImpl.h" +#include + +#include "../GainputWindows.h" + +namespace gainput +{ + +class InputDeviceMouseImplWin : public InputDeviceMouseImpl +{ +public: + InputDeviceMouseImplWin(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), MouseButtonCount + MouseAxisCount), + delta_(0) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + + // Reset mouse wheel buttons + HandleButton(device_, nextState_, delta_, MouseButton3, false); + HandleButton(device_, nextState_, delta_, MouseButton4, false); + + *state_ = nextState_; + } + + void HandleMessage(const MSG& msg) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + DeviceButtonId buttonId; + bool pressed = false; + bool moveMessage = false; + int ax = -1; + int ay = -1; + switch (msg.message) + { + case WM_LBUTTONDOWN: + buttonId = MouseButtonLeft; + pressed = true; + break; + case WM_LBUTTONUP: + buttonId = MouseButtonLeft; + pressed = false; + break; + case WM_RBUTTONDOWN: + buttonId = MouseButtonRight; + pressed = true; + break; + case WM_RBUTTONUP: + buttonId = MouseButtonRight; + pressed = false; + break; + case WM_MBUTTONDOWN: + buttonId = MouseButtonMiddle; + pressed = true; + break; + case WM_MBUTTONUP: + buttonId = MouseButtonMiddle; + pressed = false; + break; + case WM_XBUTTONDOWN: + buttonId = MouseButton4 + GET_XBUTTON_WPARAM(msg.wParam); + pressed = true; + break; + case WM_XBUTTONUP: + buttonId = MouseButton4 + GET_XBUTTON_WPARAM(msg.wParam); + pressed = false; + break; + case WM_MOUSEMOVE: + moveMessage = true; + ax = GET_X_LPARAM(msg.lParam); + ay = GET_Y_LPARAM(msg.lParam); + break; + case WM_MOUSEWHEEL: + { + int wheel = GET_WHEEL_DELTA_WPARAM(msg.wParam); + if (wheel < 0) + { + buttonId = MouseButton4; + pressed = true; + } + else if (wheel > 0) + { + buttonId = MouseButton3; + pressed = true; + } + break; + } + default: // Non-mouse message + return; + } + + if (moveMessage) + { + float x = float(ax)/float(manager_.GetDisplayWidth()); + float y = float(ay)/float(manager_.GetDisplayHeight()); + HandleAxis(device_, nextState_, delta_, MouseAxisX, x); + HandleAxis(device_, nextState_, delta_, MouseAxisY, y); + } + else + { + HandleButton(device_, nextState_, delta_, buttonId, pressed); + } + } + +private: + InputManager& manager_; + InputDevice& device_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputInputDeviceMouseWinRaw.h b/lib/source/gainput/mouse/GainputInputDeviceMouseWinRaw.h new file mode 100644 index 00000000..b9a781e7 --- /dev/null +++ b/lib/source/gainput/mouse/GainputInputDeviceMouseWinRaw.h @@ -0,0 +1,179 @@ + +#ifndef GAINPUTINPUTDEVICEMOUSEWINRAW_H_ +#define GAINPUTINPUTDEVICEMOUSEWINRAW_H_ + +#include "GainputInputDeviceMouseImpl.h" +#include + +#include "../GainputWindows.h" + +#ifndef HID_USAGE_PAGE_GENERIC +#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01) +#endif +#ifndef HID_USAGE_GENERIC_MOUSE +#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02) +#endif + +namespace gainput +{ + +class InputDeviceMouseImplWinRaw : public InputDeviceMouseImpl +{ +public: + InputDeviceMouseImplWinRaw(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + deviceState_(InputDevice::DS_UNAVAILABLE), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), MouseButtonCount + MouseAxisCount), + delta_(0), + buttonsToReset_(manager.GetAllocator()) + { + RAWINPUTDEVICE Rid[1]; + Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; + Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; + Rid[0].dwFlags = 0;//RIDEV_NOLEGACY; + Rid[0].hwndTarget = 0; + if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]))) + { + deviceState_ = InputDevice::DS_OK; + } + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_RAW; + } + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + + for (Array::const_iterator it = buttonsToReset_.begin(); + it != buttonsToReset_.end(); + ++it) + { + + HandleButton(device_, nextState_, delta, *it, false); + } + buttonsToReset_.clear(); + + *state_ = nextState_; + } + + void HandleMessage(const MSG& msg) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + if (msg.message != WM_INPUT) + { + return; + } + + UINT dwSize = 40; + static BYTE lpb[40]; + + GetRawInputData((HRAWINPUT)msg.lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)); + + RAWINPUT* raw = (RAWINPUT*)lpb; + + if (raw->header.dwType == RIM_TYPEMOUSE) + { + if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) + { + const float prevX = previousState_->GetFloat(MouseAxisX); + HandleAxis(device_, nextState_, delta_, MouseAxisX, prevX + float(raw->data.mouse.lLastX)); + const float prevY = previousState_->GetFloat(MouseAxisY); + HandleAxis(device_, nextState_, delta_, MouseAxisY, prevY + float(raw->data.mouse.lLastY)); + } + else if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) + { + HandleAxis(device_, nextState_, delta_, MouseAxisX, float( raw->data.mouse.lLastX)); + HandleAxis(device_, nextState_, delta_, MouseAxisY, float( raw->data.mouse.lLastY)); + } + + if (raw->data.mouse.usButtonFlags == RI_MOUSE_WHEEL) + { + if (SHORT(raw->data.mouse.usButtonData) < 0) + { + HandleButton(device_, nextState_, delta_, MouseButtonWheelDown, true); + buttonsToReset_.push_back(MouseButtonWheelDown); + } + else if (SHORT(raw->data.mouse.usButtonData) > 0) + { + HandleButton(device_, nextState_, delta_, MouseButtonWheelUp, true); + buttonsToReset_.push_back(MouseButtonWheelUp); + } + } + else + { + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_DOWN) + { + HandleButton(device_, nextState_,delta_, MouseButton0, true); + } + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_1_UP) + { + HandleButton(device_, nextState_, delta_, MouseButton0, false); + } + + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_DOWN) + { + HandleButton(device_, nextState_, delta_, MouseButton1, true); + } + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_2_UP) + { + HandleButton(device_, nextState_, delta_, MouseButton1, false); + } + + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_DOWN) + { + HandleButton(device_, nextState_, delta_, MouseButton2, true); + } + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_3_UP) + { + HandleButton(device_, nextState_, delta_, MouseButton2, false); + } + + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) + { + HandleButton(device_, nextState_, delta_, MouseButton5, true); + } + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) + { + HandleButton(device_, nextState_, delta_, MouseButton5, false); + } + + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN) + { + HandleButton(device_, nextState_, delta_, MouseButton6, true); + } + if (raw->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP) + { + HandleButton(device_, nextState_, delta_, MouseButton6, false); + } + } + } + } + +private: + InputManager& manager_; + InputDevice& device_; + InputDevice::DeviceState deviceState_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; + Array buttonsToReset_; +}; + +} + +#endif + diff --git a/lib/source/gainput/mouse/GainputMouseInfo.h b/lib/source/gainput/mouse/GainputMouseInfo.h new file mode 100644 index 00000000..a8c0a557 --- /dev/null +++ b/lib/source/gainput/mouse/GainputMouseInfo.h @@ -0,0 +1,48 @@ + +#ifndef GAINPUTMOUSEINFO_H_ +#define GAINPUTMOUSEINFO_H_ + +namespace gainput +{ + +namespace +{ +struct DeviceButtonInfo +{ + ButtonType type; + const char* name; +}; + +DeviceButtonInfo deviceButtonInfos[] = +{ + { BT_BOOL, "mouse_left" }, + { BT_BOOL, "mouse_middle" }, + { BT_BOOL, "mouse_right" }, + { BT_BOOL, "mouse_3" }, + { BT_BOOL, "mouse_4" }, + { BT_BOOL, "mouse_5" }, + { BT_BOOL, "mouse_6" }, + { BT_BOOL, "mouse_7" }, + { BT_BOOL, "mouse_8" }, + { BT_BOOL, "mouse_9" }, + { BT_BOOL, "mouse_10" }, + { BT_BOOL, "mouse_11" }, + { BT_BOOL, "mouse_12" }, + { BT_BOOL, "mouse_13" }, + { BT_BOOL, "mouse_14" }, + { BT_BOOL, "mouse_15" }, + { BT_BOOL, "mouse_16" }, + { BT_BOOL, "mouse_17" }, + { BT_BOOL, "mouse_18" }, + { BT_BOOL, "mouse_19" }, + { BT_BOOL, "mouse_20" }, + { BT_FLOAT, "mouse_x" }, + { BT_FLOAT, "mouse_y" } +}; +} + + +} + +#endif + diff --git a/lib/source/gainput/pad/GainputInputDevicePad.cpp b/lib/source/gainput/pad/GainputInputDevicePad.cpp new file mode 100644 index 00000000..7fb91bd0 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePad.cpp @@ -0,0 +1,274 @@ + +#include +#include + +#include "GainputInputDevicePadImpl.h" +#include +#include +#include + +#if defined(GAINPUT_PLATFORM_LINUX) + #include "GainputInputDevicePadLinux.h" +#elif defined(GAINPUT_PLATFORM_WIN) + #include "GainputInputDevicePadWin.h" +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS) + #include "GainputInputDevicePadIos.h" +#elif defined(GAINPUT_PLATFORM_MAC) + #include "GainputInputDevicePadMac.h" +#elif defined(GAINPUT_PLATFORM_ANDROID) + #include "GainputInputDevicePadAndroid.h" +#endif + +#include "GainputInputDevicePadNull.h" + +namespace gainput +{ + +namespace +{ +struct DeviceButtonInfo +{ + ButtonType type; + const char* name; +}; + +DeviceButtonInfo deviceButtonInfos[] = +{ + { BT_FLOAT, "pad_left_stick_x" }, + { BT_FLOAT, "pad_left_stick_y" }, + { BT_FLOAT, "pad_right_stick_x" }, + { BT_FLOAT, "pad_right_stick_y" }, + { BT_FLOAT, "pad_axis_4" }, + { BT_FLOAT, "pad_axis_5" }, + { BT_FLOAT, "pad_axis_6" }, + { BT_FLOAT, "pad_axis_7" }, + { BT_FLOAT, "pad_axis_8" }, + { BT_FLOAT, "pad_axis_9" }, + { BT_FLOAT, "pad_axis_10" }, + { BT_FLOAT, "pad_axis_11" }, + { BT_FLOAT, "pad_axis_12" }, + { BT_FLOAT, "pad_axis_13" }, + { BT_FLOAT, "pad_axis_14" }, + { BT_FLOAT, "pad_axis_15" }, + { BT_FLOAT, "pad_axis_16" }, + { BT_FLOAT, "pad_axis_17" }, + { BT_FLOAT, "pad_axis_18" }, + { BT_FLOAT, "pad_axis_19" }, + { BT_FLOAT, "pad_axis_20" }, + { BT_FLOAT, "pad_axis_21" }, + { BT_FLOAT, "pad_axis_22" }, + { BT_FLOAT, "pad_axis_23" }, + { BT_FLOAT, "pad_axis_24" }, + { BT_FLOAT, "pad_axis_25" }, + { BT_FLOAT, "pad_axis_26" }, + { BT_FLOAT, "pad_axis_27" }, + { BT_FLOAT, "pad_axis_28" }, + { BT_FLOAT, "pad_axis_29" }, + { BT_FLOAT, "pad_axis_30" }, + { BT_FLOAT, "pad_axis_31" }, + { BT_FLOAT, "pad_acceleration_x" }, + { BT_FLOAT, "pad_acceleration_y" }, + { BT_FLOAT, "pad_acceleration_z" }, + { BT_FLOAT, "pad_gravity_x" }, + { BT_FLOAT, "pad_gravity_y" }, + { BT_FLOAT, "pad_gravity_z" }, + { BT_FLOAT, "pad_gyroscope_x" }, + { BT_FLOAT, "pad_gyroscope_y" }, + { BT_FLOAT, "pad_gyroscope_z" }, + { BT_FLOAT, "pad_magneticfield_x" }, + { BT_FLOAT, "pad_magneticfield_y" }, + { BT_FLOAT, "pad_magneticfield_z" }, + { BT_BOOL, "pad_button_start"}, + { BT_BOOL, "pad_button_select"}, + { BT_BOOL, "pad_button_left"}, + { BT_BOOL, "pad_button_right"}, + { BT_BOOL, "pad_button_up"}, + { BT_BOOL, "pad_button_down"}, + { BT_BOOL, "pad_button_a"}, + { BT_BOOL, "pad_button_b"}, + { BT_BOOL, "pad_button_x"}, + { BT_BOOL, "pad_button_y"}, + { BT_BOOL, "pad_button_l1"}, + { BT_BOOL, "pad_button_r1"}, + { BT_BOOL, "pad_button_l2"}, + { BT_BOOL, "pad_button_r2"}, + { BT_BOOL, "pad_button_l3"}, + { BT_BOOL, "pad_button_r3"}, + { BT_BOOL, "pad_button_home"}, + { BT_BOOL, "pad_button_17"}, + { BT_BOOL, "pad_button_18"}, + { BT_BOOL, "pad_button_19"}, + { BT_BOOL, "pad_button_20"}, + { BT_BOOL, "pad_button_21"}, + { BT_BOOL, "pad_button_22"}, + { BT_BOOL, "pad_button_23"}, + { BT_BOOL, "pad_button_24"}, + { BT_BOOL, "pad_button_25"}, + { BT_BOOL, "pad_button_26"}, + { BT_BOOL, "pad_button_27"}, + { BT_BOOL, "pad_button_28"}, + { BT_BOOL, "pad_button_29"}, + { BT_BOOL, "pad_button_30"}, + { BT_BOOL, "pad_button_31"} +}; + +const unsigned PadButtonCount = PadButtonCount_; +const unsigned PadAxisCount = PadButtonAxisCount_; + +} + + +InputDevicePad::InputDevicePad(InputManager& manager, DeviceId device, unsigned index, DeviceVariant /*variant*/) : + InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_PAD) : 0), + impl_(0) +{ + state_ = manager.GetAllocator().New(manager.GetAllocator(), PadButtonCount + PadAxisCount); + GAINPUT_ASSERT(state_); + previousState_ = manager.GetAllocator().New(manager.GetAllocator(), PadButtonCount + PadAxisCount); + GAINPUT_ASSERT(previousState_); + +#if defined(GAINPUT_PLATFORM_LINUX) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#elif defined(GAINPUT_PLATFORM_WIN) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#elif defined(GAINPUT_PLATFORM_MAC) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#elif defined(GAINPUT_PLATFORM_ANDROID) + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); +#endif + + if (!impl_) + { + impl_ = manager.GetAllocator().New(manager, *this, index_, *state_, *previousState_); + } + + GAINPUT_ASSERT(impl_); + + SetDeadZone(PadButtonLeftStickX, 0.15f); + SetDeadZone(PadButtonLeftStickY, 0.15f); + SetDeadZone(PadButtonRightStickX, 0.15f); + SetDeadZone(PadButtonRightStickY, 0.15f); +} + +InputDevicePad::~InputDevicePad() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); + manager_.GetAllocator().Delete(impl_); +} + +void +InputDevicePad::InternalUpdate(InputDeltaState* delta) +{ + impl_->Update(delta); + + if ((manager_.IsDebugRenderingEnabled() || IsDebugRenderingEnabled()) + && manager_.GetDebugRenderer()) + { + DebugRenderer* debugRenderer = manager_.GetDebugRenderer(); + InputState* state = GetInputState(); + char buf[64]; + float x = 0.4f; + float y = 0.2f; + for (int i = PadButtonStart; i < PadButtonMax_; ++i) + { + if (state->GetBool(i)) + { + GetButtonName(i, buf, 64); + debugRenderer->DrawText(x, y, buf); + y += 0.025f; + } + } + + x = 0.8f; + y = 0.2f; + const float circleRadius = 0.1f; + debugRenderer->DrawCircle(x, y, 0.01f); + debugRenderer->DrawCircle(x, y, circleRadius); + float dirX = state->GetFloat(PadButtonLeftStickX) * circleRadius; + float dirY = state->GetFloat(PadButtonLeftStickY) * circleRadius; + debugRenderer->DrawLine(x, y, x + dirX, y + dirY); + + y = 0.6f; + debugRenderer->DrawCircle(x, y, 0.01f); + debugRenderer->DrawCircle(x, y, circleRadius); + dirX = state->GetFloat(PadButtonRightStickX) * circleRadius; + dirY = state->GetFloat(PadButtonRightStickY) * circleRadius; + debugRenderer->DrawLine(x, y, x + dirX, y + dirY); + } +} + +InputDevice::DeviceState +InputDevicePad::InternalGetState() const +{ + return impl_->GetState(); +} + +InputDevice::DeviceVariant +InputDevicePad::GetVariant() const +{ + return impl_->GetVariant(); +} + +bool +InputDevicePad::IsValidButtonId(DeviceButtonId deviceButton) const +{ + return impl_->IsValidButton(deviceButton); +} + +size_t +InputDevicePad::GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + GAINPUT_ASSERT(outButtons); + GAINPUT_ASSERT(maxButtonCount > 0); + return CheckAllButtonsDown(outButtons, maxButtonCount, PadButtonLeftStickX, PadButtonMax_); +} + +size_t +InputDevicePad::GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + GAINPUT_ASSERT(buffer); + GAINPUT_ASSERT(bufferLength > 0); + strncpy(buffer, deviceButtonInfos[deviceButton].name, bufferLength); + buffer[bufferLength-1] = 0; + const size_t nameLen = strlen(deviceButtonInfos[deviceButton].name); + return nameLen >= bufferLength ? bufferLength : nameLen+1; +} + +ButtonType +InputDevicePad::GetButtonType(DeviceButtonId deviceButton) const +{ + return deviceButtonInfos[deviceButton].type; +} + +DeviceButtonId +InputDevicePad::GetButtonByName(const char* name) const +{ + GAINPUT_ASSERT(name); + for (unsigned i = 0; i < PadButtonCount + PadAxisCount; ++i) + { + if (strcmp(name, deviceButtonInfos[i].name) == 0) + { + return DeviceButtonId(i); + } + } + return InvalidDeviceButtonId; +} + +InputState* +InputDevicePad::GetNextInputState() +{ + return impl_->GetNextInputState(); +} + +bool +InputDevicePad::Vibrate(float leftMotor, float rightMotor) +{ + return impl_->Vibrate(leftMotor, rightMotor); +} + +} + diff --git a/lib/source/gainput/pad/GainputInputDevicePadAndroid.h b/lib/source/gainput/pad/GainputInputDevicePadAndroid.h new file mode 100644 index 00000000..5abf4aaf --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadAndroid.h @@ -0,0 +1,79 @@ + +#ifndef GAINPUTINPUTDEVICEPADANDROID_H_ +#define GAINPUTINPUTDEVICEPADANDROID_H_ + +#include "GainputInputDevicePadImpl.h" +#include + +namespace gainput +{ + +class InputDevicePadImplAndroid : public InputDevicePadImpl +{ +public: + InputDevicePadImplAndroid(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), PadButtonMax_), + delta_(0), + index_(index), + deviceState_(InputDevice::DS_UNAVAILABLE) + { + (void)previousState; + GAINPUT_ASSERT(index_ < MaxPadCount); + } + + ~InputDevicePadImplAndroid() + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + void SetState(InputDevice::DeviceState state) + { + deviceState_ = state; + } + + bool IsValidButton(DeviceButtonId deviceButton) const + { + return deviceButton < PadButtonMax_; + } + + bool Vibrate(float /*leftMotor*/, float /*rightMotor*/) + { + return false; // TODO + } + + InputState* GetNextInputState() { return &nextState_; } + +private: + InputManager& manager_; + InputDevice& device_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; + unsigned index_; + InputDevice::DeviceState deviceState_; + +}; + +} + +#endif diff --git a/lib/source/gainput/pad/GainputInputDevicePadImpl.h b/lib/source/gainput/pad/GainputInputDevicePadImpl.h new file mode 100644 index 00000000..6dd96dfb --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadImpl.h @@ -0,0 +1,23 @@ + +#ifndef GAINPUTINPUTDEVICEPADIMPL_H_ +#define GAINPUTINPUTDEVICEPADIMPL_H_ + +namespace gainput +{ + +class InputDevicePadImpl +{ +public: + virtual ~InputDevicePadImpl() { } + virtual InputDevice::DeviceVariant GetVariant() const = 0; + virtual InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } + virtual void Update(InputDeltaState* delta) = 0; + virtual bool IsValidButton(DeviceButtonId deviceButton) const = 0; + virtual bool Vibrate(float leftMotor, float rightMotor) = 0; + virtual InputState* GetNextInputState() { return 0; } +}; + +} + +#endif + diff --git a/lib/source/gainput/pad/GainputInputDevicePadIos.h b/lib/source/gainput/pad/GainputInputDevicePadIos.h new file mode 100644 index 00000000..00816883 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadIos.h @@ -0,0 +1,60 @@ + +#ifndef GAINPUTINPUTDEVICEPADIOS_H_ +#define GAINPUTINPUTDEVICEPADIOS_H_ + +#include + +namespace gainput +{ + +class InputDevicePadImplIos : public InputDevicePadImpl +{ +public: + InputDevicePadImplIos(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState); + ~InputDevicePadImplIos(); + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta); + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + bool IsValidButton(DeviceButtonId deviceButton) const; + + typedef gainput::Array GlobalControllerList; + static GlobalControllerList* mappedControllers_; + + bool Vibrate(float leftMotor, float rightMotor) + { + return false; + } + +private: + InputManager& manager_; + InputDevice& device_; + unsigned index_; + InputState& state_; + InputState& previousState_; + InputDevice::DeviceState deviceState_; + + bool pausePressed_; + bool isMicro_; + bool isNormal_; + bool isExtended_; + bool supportsMotion_; + bool isRemote_; + void* gcController_; + + void UpdateRemote_(InputDeltaState* delta); + void UpdateGamepad_(InputDeltaState* delta); +}; + +} + +#endif diff --git a/lib/source/gainput/pad/GainputInputDevicePadIos.mm b/lib/source/gainput/pad/GainputInputDevicePadIos.mm new file mode 100644 index 00000000..4d989ae6 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadIos.mm @@ -0,0 +1,364 @@ +#include + +#if defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS) + +#include "GainputInputDevicePadImpl.h" +#include +#include +#include + +#include "GainputInputDevicePadIos.h" + +#import + +namespace gainput +{ + +InputDevicePadImplIos::GlobalControllerList* InputDevicePadImplIos::mappedControllers_; + +namespace +{ +static bool isAppleTvRemote(GCController* controller) +{ +#if defined(GAINPUT_PLATFORM_TVOS) + if (!controller) + { + return false; + } + GCGamepad* gamepad = [controller gamepad]; + GCMicroGamepad* microGamepad = [controller microGamepad]; + GCExtendedGamepad* extgamepad = [controller extendedGamepad]; + GCMotion* motion = [controller motion]; + + return + microGamepad != NULL && + motion != NULL && + gamepad == NULL && + extgamepad == NULL; +#else + return false; +#endif +} + +static GCController* getGcController(void* c) +{ + return static_cast(c); +} + +static bool IsValidHardwareController(GCController const * controller) +{ + if (controller == NULL) return false; + + NSArray* controllers = [GCController controllers]; + const std::size_t controllerCount = [controllers count]; + + for (std::size_t i = 0; i < controllerCount; ++i) + { + GCController * currentController = controllers[i]; + + if (currentController == controller) + { + return true; + } + } + return false; +} + +// Removes all invalid controller mappings (controllers that are not available anymore) +static void CleanDisconnectedControllersFromMapping() +{ + if (!InputDevicePadImplIos::mappedControllers_) + { + return; + } + InputDevicePadImplIos::GlobalControllerList& mapping = *InputDevicePadImplIos::mappedControllers_; + for (InputDevicePadImplIos::GlobalControllerList::iterator it = mapping.begin(); it != mapping.end(); ) + { + GCController const* controller = static_cast(*it); + if (!IsValidHardwareController(controller)) + { + it = mapping.erase(it); + } + else + { + ++it; + } + } +} + +// Returns true if this controller is already mapped to a gainput pad, otherwise false +static bool IsMapped(GCController* controller) +{ + if (0 == controller + || !InputDevicePadImplIos::mappedControllers_) + { + return false; + } + InputDevicePadImplIos::GlobalControllerList& mapping = *InputDevicePadImplIos::mappedControllers_; + for(std::size_t index = 0; index < mapping.size(); ++index) + { + GCController const* mapped_controller = static_cast(mapping[index]); + if (controller == mapped_controller) + { + return true; + } + } + return false; +} + +/// Returns the first hardware controller that is not yet mapped +static GCController* GetFirstNonMappedController() +{ + NSArray* controllers = [GCController controllers]; + const std::size_t controllerCount = [controllers count]; + for (std::size_t i = 0; i < controllerCount; ++i) + { + GCController* currentController = controllers[i]; + if (!IsMapped(currentController)) + { + return currentController; + } + } + return 0; +} + +} + +InputDevicePadImplIos::InputDevicePadImplIos(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + index_(index), + state_(state), + previousState_(previousState), + deviceState_(InputDevice::DS_UNAVAILABLE), + pausePressed_(false), + isMicro_(false), + isNormal_(false), + isExtended_(false), + supportsMotion_(false), + isRemote_(false), + gcController_(0) +{ + (void)&this->manager_; + (void)&this->previousState_; + + device_.SetDeadZone(PadButtonGyroscopeX, 0.1); + device_.SetDeadZone(PadButtonGyroscopeY, 0.1); + device_.SetDeadZone(PadButtonGyroscopeZ, 0.1); + + if (!mappedControllers_) + { + mappedControllers_ = manager.GetAllocator().New(manager.GetAllocator()); + } +} + +InputDevicePadImplIos::~InputDevicePadImplIos() +{ +} + +void InputDevicePadImplIos::Update(InputDeltaState* delta) +{ + bool validHardwareController = IsValidHardwareController(getGcController(gcController_)); + + if (!validHardwareController) + { + CleanDisconnectedControllersFromMapping(); + GCController * firstNonMappedController = GetFirstNonMappedController(); + + if (firstNonMappedController) + { + GCControllerPlayerIndex newIndex = static_cast(index_); + if (firstNonMappedController.playerIndex != newIndex) + { + firstNonMappedController.playerIndex = newIndex; + } + + // register pause menu button handler + __block InputDevicePadImplIos* block_deviceImpl = this; + firstNonMappedController.controllerPausedHandler = ^(GCController* controller) + { + block_deviceImpl->pausePressed_ = true; + }; + } + else + { + deviceState_ = InputDevice::DS_UNAVAILABLE; + return; + } + gcController_ = firstNonMappedController; + } + + GAINPUT_ASSERT(gcController_); + if (!gcController_) + { + deviceState_ = InputDevice::DS_UNAVAILABLE; + isExtended_ = false; + supportsMotion_ = false; + return; + } + + deviceState_ = InputDevice::DS_OK; + + GCController* controller = getGcController(gcController_); + + isRemote_ = isAppleTvRemote(controller); + isExtended_ = [controller extendedGamepad] != 0; +#if defined(GAINPUT_PLATFORM_TVOS) + isMicro_ = [controller microGamepad] != 0; +#else + isMicro_ = false; +#endif + isNormal_ = [controller gamepad] != 0; + supportsMotion_ = [controller motion] != 0; + + if (isRemote_) + { + UpdateRemote_(delta); + } + else + { + UpdateGamepad_(delta); + } + + HandleButton(device_, state_, delta, PadButtonHome, pausePressed_); + pausePressed_ = false; +} + +void InputDevicePadImplIos::UpdateRemote_(InputDeltaState* delta) +{ +#if defined(GAINPUT_PLATFORM_TVOS) + GCController* controller = getGcController(gcController_); + + if (isMicro_) + { + GCMicroGamepad* gamepad = [controller microGamepad]; + gamepad.reportsAbsoluteDpadValues = YES; + + HandleButton(device_, state_, delta, PadButtonA, gamepad.buttonA.pressed); // Force push on touch area + HandleButton(device_, state_, delta, PadButtonX, gamepad.buttonX.pressed); // Play/Pause Button + + HandleAxis(device_, state_, delta, PadButtonAxis30, gamepad.dpad.xAxis.value); // Touch area X + HandleAxis(device_, state_, delta, PadButtonAxis31, gamepad.dpad.yAxis.value); // Touch area Y + } + + if (supportsMotion_) + { + GCMotion* motion = [controller motion]; + + HandleAxis(device_, state_, delta, PadButtonAccelerationX, motion.userAcceleration.x); + HandleAxis(device_, state_, delta, PadButtonAccelerationY, motion.userAcceleration.y); + HandleAxis(device_, state_, delta, PadButtonAccelerationZ, motion.userAcceleration.z); + + HandleAxis(device_, state_, delta, PadButtonGravityX, motion.gravity.x); + HandleAxis(device_, state_, delta, PadButtonGravityY, motion.gravity.y); + HandleAxis(device_, state_, delta, PadButtonGravityZ, motion.gravity.z); + + // The Siri Remote does not have a gyro. + HandleAxis(device_, state_, delta, PadButtonGyroscopeX, 0.0f); + HandleAxis(device_, state_, delta, PadButtonGyroscopeY, 0.0f); + HandleAxis(device_, state_, delta, PadButtonGyroscopeZ, 0.0f); + } +#endif +} + +void InputDevicePadImplIos::UpdateGamepad_(InputDeltaState* delta) +{ + GCController* controller = getGcController(gcController_); + + if (isExtended_) + { + GCExtendedGamepad* gamepad = [controller extendedGamepad]; + + HandleButton(device_, state_, delta, PadButtonL1, gamepad.leftShoulder.pressed); + HandleButton(device_, state_, delta, PadButtonR1, gamepad.rightShoulder.pressed); + + HandleButton(device_, state_, delta, PadButtonLeft, gamepad.dpad.left.pressed); + HandleButton(device_, state_, delta, PadButtonRight, gamepad.dpad.right.pressed); + HandleButton(device_, state_, delta, PadButtonUp, gamepad.dpad.up.pressed); + HandleButton(device_, state_, delta, PadButtonDown, gamepad.dpad.down.pressed); + + HandleButton(device_, state_, delta, PadButtonA, gamepad.buttonA.pressed); + HandleButton(device_, state_, delta, PadButtonB, gamepad.buttonB.pressed); + HandleButton(device_, state_, delta, PadButtonX, gamepad.buttonX.pressed); + HandleButton(device_, state_, delta, PadButtonY, gamepad.buttonY.pressed); + + HandleAxis(device_, state_, delta, PadButtonLeftStickX, gamepad.leftThumbstick.xAxis.value); + HandleAxis(device_, state_, delta, PadButtonLeftStickY, gamepad.leftThumbstick.yAxis.value); + + HandleAxis(device_, state_, delta, PadButtonRightStickX, gamepad.rightThumbstick.xAxis.value); + HandleAxis(device_, state_, delta, PadButtonRightStickY, gamepad.rightThumbstick.yAxis.value); + + HandleButton(device_, state_, delta, PadButtonL2, gamepad.leftTrigger.pressed); + HandleAxis(device_, state_, delta, PadButtonAxis4, gamepad.leftTrigger.value); + HandleButton(device_, state_, delta, PadButtonR2, gamepad.rightTrigger.pressed); + HandleAxis(device_, state_, delta, PadButtonAxis5, gamepad.rightTrigger.value); + } + else if (isNormal_) + { + GCGamepad* gamepad = [controller gamepad]; + + HandleButton(device_, state_, delta, PadButtonL1, gamepad.leftShoulder.pressed); + HandleButton(device_, state_, delta, PadButtonR1, gamepad.rightShoulder.pressed); + + HandleButton(device_, state_, delta, PadButtonLeft, gamepad.dpad.left.pressed); + HandleButton(device_, state_, delta, PadButtonRight, gamepad.dpad.right.pressed); + HandleButton(device_, state_, delta, PadButtonUp, gamepad.dpad.up.pressed); + HandleButton(device_, state_, delta, PadButtonDown, gamepad.dpad.down.pressed); + + HandleButton(device_, state_, delta, PadButtonA, gamepad.buttonA.pressed); + HandleButton(device_, state_, delta, PadButtonB, gamepad.buttonB.pressed); + HandleButton(device_, state_, delta, PadButtonX, gamepad.buttonX.pressed); + HandleButton(device_, state_, delta, PadButtonY, gamepad.buttonY.pressed); + } +#if defined(GAINPUT_PLATFORM_TVOS) + else if (isMicro_) + { + GCMicroGamepad * gamepad = [controller microGamepad]; + gamepad.reportsAbsoluteDpadValues = YES; + HandleButton(device_, state_, delta, PadButtonA, gamepad.buttonA.pressed); // Force push on touch area + HandleButton(device_, state_, delta, PadButtonX, gamepad.buttonX.pressed); // Play/Pause Button + + HandleAxis(device_, state_, delta, PadButtonAxis30, gamepad.dpad.xAxis.value); // Touch area X + HandleAxis(device_, state_, delta, PadButtonAxis31, gamepad.dpad.yAxis.value); // Touch area Y + } +#endif + + if (GCMotion* motion = [controller motion]) + { + HandleAxis(device_, state_, delta, PadButtonAccelerationX, motion.userAcceleration.x); + HandleAxis(device_, state_, delta, PadButtonAccelerationY, motion.userAcceleration.y); + HandleAxis(device_, state_, delta, PadButtonAccelerationZ, motion.userAcceleration.z); + + HandleAxis(device_, state_, delta, PadButtonGravityX, motion.gravity.x); + HandleAxis(device_, state_, delta, PadButtonGravityY, motion.gravity.y); + HandleAxis(device_, state_, delta, PadButtonGravityZ, motion.gravity.z); + + const float gyroX = 2.0f * (motion.attitude.x * motion.attitude.z + motion.attitude.w * motion.attitude.y); + const float gyroY = 2.0f * (motion.attitude.y * motion.attitude.z - motion.attitude.w * motion.attitude.x); + const float gyroZ = 1.0f - 2.0f * (motion.attitude.x * motion.attitude.x + motion.attitude.y * motion.attitude.y); + + HandleAxis(device_, state_, delta, PadButtonGyroscopeX, gyroX); + HandleAxis(device_, state_, delta, PadButtonGyroscopeY, gyroY); + HandleAxis(device_, state_, delta, PadButtonGyroscopeZ, gyroZ); + } +} + +bool InputDevicePadImplIos::IsValidButton(DeviceButtonId deviceButton) const +{ + if (supportsMotion_ && deviceButton >= PadButtonAccelerationX && deviceButton <= PadButtonMagneticFieldZ) + { + return true; + } + if (isExtended_) + { + return (deviceButton >= PadButtonLeftStickX && deviceButton <= PadButtonAxis5) + || (deviceButton >= PadButtonLeft && deviceButton <= PadButtonR2) + || deviceButton == PadButtonHome; + } + return (deviceButton >= PadButtonLeft && deviceButton <= PadButtonR1) + || deviceButton == PadButtonHome; +} + +} + +#endif diff --git a/lib/source/gainput/pad/GainputInputDevicePadLinux.h b/lib/source/gainput/pad/GainputInputDevicePadLinux.h new file mode 100644 index 00000000..bc04b182 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadLinux.h @@ -0,0 +1,285 @@ + +#ifndef GAINPUTINPUTDEVICEPADLINUX_H_ +#define GAINPUTINPUTDEVICEPADLINUX_H_ + +#include +#include +#include +#include + +// Cf. http://www.kernel.org/doc/Documentation/input/joystick-api.txt +// Cf. http://ps3.jim.sh/sixaxis/usb/ + + +namespace gainput +{ + +/// Maximum negative and positive value for an axis. +const float MaxAxisValue = 32767.0f; + +static const char* PadDeviceIds[MaxPadCount] = +{ + "/dev/input/js0", + "/dev/input/js1", + "/dev/input/js2", + "/dev/input/js3", + "/dev/input/js4", + "/dev/input/js5", + "/dev/input/js6", + "/dev/input/js7", + "/dev/input/js8", + "/dev/input/js9" +}; + +class InputDevicePadImplLinux : public InputDevicePadImpl +{ +public: + InputDevicePadImplLinux(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(state), + index_(index), + deviceState_(InputDevice::DS_UNAVAILABLE), + fd_(-1), + buttonDialect_(manager_.GetAllocator()) + { + GAINPUT_ASSERT(index_ < MaxPadCount); + CheckForDevice(); + } + + ~InputDevicePadImplLinux() + { + if (fd_ != -1) + { + close(fd_); + } + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + CheckForDevice(); + + if (fd_ < 0) + { + return; + } + + js_event event; + int c; + + while ( (c = read(fd_, &event, sizeof(js_event))) == sizeof(js_event) ) + { + event.type &= ~JS_EVENT_INIT; + if (event.type == JS_EVENT_AXIS) + { + GAINPUT_ASSERT(event.number < PadButtonAxisCount_); + DeviceButtonId buttonId = event.number; + + const float value = float(event.value)/MaxAxisValue; + + if (axisDialect_.count(buttonId)) + { + buttonId = axisDialect_[buttonId]; + } + + if (buttonId == PadButtonUp) + { + HandleButton(device_, state_, delta, PadButtonUp, value < 0.0f); + HandleButton(device_, state_, delta, PadButtonDown, value > 0.0f); + } + else if (buttonId == PadButtonLeft) + { + HandleButton(device_, state_, delta, PadButtonLeft, value < 0.0f); + HandleButton(device_, state_, delta, PadButtonRight, value > 0.0f); + } + else + { + HandleAxis(device_, state_, delta, buttonId, value); + } + } + else if (event.type == JS_EVENT_BUTTON) + { + GAINPUT_ASSERT(event.number < PadButtonCount_); + if (buttonDialect_.count(event.number)) + { + DeviceButtonId buttonId = buttonDialect_[event.number]; + const bool value(event.value); + + HandleButton(device_, state_, delta, buttonId, value); + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Unknown pad button #%d: %d\n", int(event.number), event.value); + } +#endif + } + } + GAINPUT_ASSERT(c == -1); + + if (c == -1 + && (errno == EBADF || errno == ECONNRESET || errno == ENOTCONN || errno == EIO || errno == ENXIO || errno == ENODEV)) + { +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("Pad lost.\n"); +#endif + deviceState_ = InputDevice::DS_UNAVAILABLE; + fd_ = -1; + } + } + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + bool IsValidButton(DeviceButtonId deviceButton) const + { + if (buttonDialect_.empty()) + { + return deviceButton < PadButtonMax_; + } + + for (HashMap::const_iterator it = buttonDialect_.begin(); + it != buttonDialect_.end(); + ++it) + { + if (it->second == deviceButton) + { + return true; + } + } + + for (HashMap::const_iterator it = axisDialect_.begin(); + it != axisDialect_.end(); + ++it) + { + if (it->second == deviceButton) + { + return true; + } + } + + return false; + } + + bool Vibrate(float /*leftMotor*/, float /*rightMotor*/) + { + return false; // TODO + } + +private: + InputManager& manager_; + InputDevice& device_; + InputState& state_; + unsigned index_; + InputDevice::DeviceState deviceState_; + int fd_; + HashMap buttonDialect_; + HashMap axisDialect_; + + void CheckForDevice() + { + if (fd_ != -1) + { + return; + } + + deviceState_ = InputDevice::DS_UNAVAILABLE; + + fd_ = open(PadDeviceIds[index_], O_RDONLY | O_NONBLOCK); + if (fd_ < 0) + { + return; + } + GAINPUT_ASSERT(fd_ >= 0); + +#ifdef GAINPUT_DEBUG + char axesCount; + ioctl(fd_, JSIOCGAXES, &axesCount); + GAINPUT_LOG("Axes count: %d\n", int(axesCount)); + + int driverVersion; + ioctl(fd_, JSIOCGVERSION, &driverVersion); + GAINPUT_LOG("Driver version: %d\n", driverVersion); +#endif + + char name[128] = ""; + if (ioctl(fd_, JSIOCGNAME(sizeof(name)), name) < 0) + { + strncpy(name, "Unknown", sizeof(name)); + } +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("Name: %s\n", name); +#endif + + for (unsigned i = PadButtonLeftStickX; i < PadButtonAxisCount_; ++i) + { + axisDialect_[i] = i; + } + + if (strcmp(name, "Sony PLAYSTATION(R)3 Controller") == 0) + { +#ifdef GAINPUT_DEBUG + GAINPUT_LOG(" --> known controller\n"); +#endif + buttonDialect_[0] = PadButtonSelect; + buttonDialect_[1] = PadButtonL3; + buttonDialect_[2] = PadButtonR3; + buttonDialect_[3] = PadButtonStart; + buttonDialect_[4] = PadButtonUp; + buttonDialect_[5] = PadButtonRight; + buttonDialect_[6] = PadButtonDown; + buttonDialect_[7] = PadButtonLeft; + buttonDialect_[8] = PadButtonL2; + buttonDialect_[9] = PadButtonR2; + buttonDialect_[10] = PadButtonL1; + buttonDialect_[11] = PadButtonR1; + buttonDialect_[12] = PadButtonY; + buttonDialect_[13] = PadButtonB; + buttonDialect_[14] = PadButtonA; + buttonDialect_[15] = PadButtonX; + buttonDialect_[16] = PadButtonHome; + } + else if (strcmp(name, "Microsoft X-Box 360 pad") == 0) + { +#ifdef GAINPUT_DEBUG + GAINPUT_LOG(" --> known controller\n"); +#endif + buttonDialect_[6] = PadButtonSelect; + buttonDialect_[9] = PadButtonL3; + buttonDialect_[10] = PadButtonR3; + buttonDialect_[7] = PadButtonStart; + buttonDialect_[4] = PadButtonL1; + buttonDialect_[5] = PadButtonR1; + buttonDialect_[3] = PadButtonY; + buttonDialect_[1] = PadButtonB; + buttonDialect_[0] = PadButtonA; + buttonDialect_[2] = PadButtonX; + buttonDialect_[8] = PadButtonHome; + + axisDialect_[3] = PadButtonRightStickX; + axisDialect_[4] = PadButtonRightStickY; + axisDialect_[2] = PadButtonAxis4; + axisDialect_[5] = PadButtonAxis5; + axisDialect_[7] = PadButtonUp; + axisDialect_[6] = PadButtonLeft; + + // Dummy entries for IsValidButton + axisDialect_[-1] = PadButtonDown; + axisDialect_[-2] = PadButtonRight; + } + + deviceState_ = InputDevice::DS_OK; + } +}; + +} + +#endif + diff --git a/lib/source/gainput/pad/GainputInputDevicePadMac.cpp b/lib/source/gainput/pad/GainputInputDevicePadMac.cpp new file mode 100644 index 00000000..0f174f06 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadMac.cpp @@ -0,0 +1,413 @@ + +#include + +#ifdef GAINPUT_PLATFORM_MAC + +#include "GainputInputDevicePadImpl.h" +#include +#include +#include + +#include "GainputInputDevicePadMac.h" + +#import +#import +#import + + +namespace gainput +{ + +extern bool MacIsApplicationKey(); + +namespace { + +static const unsigned kMaxPads = 16; +static bool usedPadIndices_[kMaxPads] = { false }; + +static inline float FixUpAnalog(float analog, const float minAxis, const float maxAxis, bool symmetric) +{ + analog = analog < minAxis ? minAxis : analog > maxAxis ? maxAxis : analog; // clamp + analog -= minAxis; + analog /= (Abs(minAxis) + Abs(maxAxis))*(symmetric ? 0.5f : 1.0f); + if (symmetric) + { + analog -= 1.0f; + } + return analog; +} + +static void OnDeviceInput(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) +{ + if (!MacIsApplicationKey()) + { + return; + } + + IOHIDElementRef elem = IOHIDValueGetElement(value); + + InputDevicePadImplMac* device = reinterpret_cast(inContext); + GAINPUT_ASSERT(device); + + uint32_t usagePage = IOHIDElementGetUsagePage(elem); + uint32_t usage = IOHIDElementGetUsage(elem); + + if (IOHIDElementGetReportCount(elem) > 1 + || (usagePage == kHIDPage_GenericDesktop && usage == kHIDUsage_GD_Pointer) ) + { + return; + } + + if (usagePage >= kHIDPage_VendorDefinedStart) + { + return; + } + + InputManager& manager = device->manager_; + CFIndex state = (int)IOHIDValueGetIntegerValue(value); + float analog = IOHIDValueGetScaledValue(value, kIOHIDValueScaleTypePhysical); + + if (usagePage == kHIDPage_Button && device->buttonDialect_.count(usage)) + { + const DeviceButtonId buttonId = device->buttonDialect_[usage]; + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, buttonId, state != 0); + } + else if (usagePage == kHIDPage_GenericDesktop) + { + if (usage == kHIDUsage_GD_Hatswitch) + { + int dpadX = 0; + int dpadY = 0; + switch(state) + { + case 0: dpadX = 0; dpadY = 1; break; + case 1: dpadX = 1; dpadY = 1; break; + case 2: dpadX = 1; dpadY = 0; break; + case 3: dpadX = 1; dpadY = -1; break; + case 4: dpadX = 0; dpadY = -1; break; + case 5: dpadX = -1; dpadY = -1; break; + case 6: dpadX = -1; dpadY = 0; break; + case 7: dpadX = -1; dpadY = 1; break; + default: dpadX = 0; dpadY = 0; break; + } + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, PadButtonLeft, dpadX < 0); + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, PadButtonRight, dpadX > 0); + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, PadButtonUp, dpadY > 0); + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, PadButtonDown, dpadY < 0); + } + else if (device->axisDialect_.count(usage)) + { + const DeviceButtonId buttonId = device->axisDialect_[usage]; + if (buttonId == PadButtonAxis4 || buttonId == PadButtonAxis5) + { + analog = FixUpAnalog(analog, device->minTriggerAxis_, device->maxTriggerAxis_, false); + } + else if (buttonId == PadButtonLeftStickY || buttonId == PadButtonRightStickY) + { + analog = -FixUpAnalog(analog, device->minAxis_, device->maxAxis_, true); + } + else + { + analog = FixUpAnalog(analog, device->minAxis_, device->maxAxis_, true); + } + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, buttonId, analog); + } + else if (device->buttonDialect_.count(usage)) + { + const DeviceButtonId buttonId = device->buttonDialect_[usage]; + manager.EnqueueConcurrentChange(device->device_, device->nextState_, device->delta_, buttonId, state != 0); + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Unmapped button (generic): %d\n", usage); + } +#endif + } +#ifdef GAINPUT_DEBUG + else + { + GAINPUT_LOG("Unmapped button: %d\n", usage); + } +#endif +} + +static void OnDeviceConnected(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) +{ + InputDevicePadImplMac* device = reinterpret_cast(inContext); + GAINPUT_ASSERT(device); + + if (device->deviceState_ != InputDevice::DS_UNAVAILABLE) + { + return; + } + + for (unsigned i = 0; i < device->index_ && i < kMaxPads; ++i) + { + if (!usedPadIndices_[i]) + { + return; + } + } + + if (device->index_ < kMaxPads) + { + usedPadIndices_[device->index_] = true; + } + device->deviceState_ = InputDevice::DS_OK; + + long vendorId = 0; + long productId = 0; + + if (CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDVendorIDKey) )) + { + if (CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef)) + { + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &vendorId); + } + } + if (CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, CFSTR(kIOHIDProductIDKey) )) + { + if (CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef)) + { + CFNumberGetValue((CFNumberRef)tCFTypeRef, kCFNumberSInt32Type, &productId); + } + } + + if (vendorId == 0x054c && (productId == 0x5c4 || productId == 0x9cc)) // Sony DualShock 4 + { + device->minAxis_ = 0; + device->maxAxis_ = 256; + device->minTriggerAxis_ = device->minAxis_; + device->maxTriggerAxis_ = device->maxAxis_; + device->axisDialect_[kHIDUsage_GD_X] = PadButtonLeftStickX; + device->axisDialect_[kHIDUsage_GD_Y] = PadButtonLeftStickY; + device->axisDialect_[kHIDUsage_GD_Z] = PadButtonRightStickX; + device->axisDialect_[kHIDUsage_GD_Rz] = PadButtonRightStickY; + device->axisDialect_[kHIDUsage_GD_Rx] = PadButtonAxis4; + device->axisDialect_[kHIDUsage_GD_Ry] = PadButtonAxis5; + device->buttonDialect_[0x09] = PadButtonSelect; + device->buttonDialect_[0x0b] = PadButtonL3; + device->buttonDialect_[0x0c] = PadButtonR3; + device->buttonDialect_[0x0A] = PadButtonStart; + device->buttonDialect_[0xfffffff0] = PadButtonUp; + device->buttonDialect_[0xfffffff1] = PadButtonRight; + device->buttonDialect_[0xfffffff2] = PadButtonDown; + device->buttonDialect_[0xfffffff3] = PadButtonLeft; + device->buttonDialect_[0x05] = PadButtonL1; + device->buttonDialect_[0x07] = PadButtonL2; + device->buttonDialect_[0x06] = PadButtonR1; + device->buttonDialect_[0x08] = PadButtonR2; + device->buttonDialect_[0x04] = PadButtonY; + device->buttonDialect_[0x03] = PadButtonB; + device->buttonDialect_[0x02] = PadButtonA; + device->buttonDialect_[0x01] = PadButtonX; + device->buttonDialect_[0x0d] = PadButtonHome; + device->buttonDialect_[0x0e] = PadButton17; // Touch pad + } + else if (vendorId == 0x054c && productId == 0x268) // Sony DualShock 3 + { + device->minAxis_ = 0; + device->maxAxis_ = 256; + device->minTriggerAxis_ = device->minAxis_; + device->maxTriggerAxis_ = device->maxAxis_; + device->axisDialect_[kHIDUsage_GD_X] = PadButtonLeftStickX; + device->axisDialect_[kHIDUsage_GD_Y] = PadButtonLeftStickY; + device->axisDialect_[kHIDUsage_GD_Z] = PadButtonRightStickX; + device->axisDialect_[kHIDUsage_GD_Rz] = PadButtonRightStickY; + device->axisDialect_[kHIDUsage_GD_Rx] = PadButtonAxis4; + device->axisDialect_[kHIDUsage_GD_Ry] = PadButtonAxis5; + //device->buttonDialect_[0] = PadButtonSelect; + device->buttonDialect_[2] = PadButtonL3; + device->buttonDialect_[3] = PadButtonR3; + device->buttonDialect_[4] = PadButtonStart; + device->buttonDialect_[5] = PadButtonUp; + device->buttonDialect_[6] = PadButtonRight; + device->buttonDialect_[7] = PadButtonDown; + device->buttonDialect_[8] = PadButtonLeft; + device->buttonDialect_[11] = PadButtonL1; + device->buttonDialect_[9] = PadButtonL2; + device->buttonDialect_[12] = PadButtonR1; + device->buttonDialect_[10] = PadButtonR2; + device->buttonDialect_[13] = PadButtonY; + device->buttonDialect_[14] = PadButtonB; + device->buttonDialect_[15] = PadButtonA; + device->buttonDialect_[16] = PadButtonX; + device->buttonDialect_[17] = PadButtonHome; + } + else if (vendorId == 0x045e && (productId == 0x028E || productId == 0x028F || productId == 0x02D1)) // Microsoft 360 Controller wired/wireless, Xbox One Controller + { + device->minAxis_ = -(1<<15); + device->maxAxis_ = 1<<15; + device->minTriggerAxis_ = 0; + device->maxTriggerAxis_ = 255; + device->axisDialect_[kHIDUsage_GD_X] = PadButtonLeftStickX; + device->axisDialect_[kHIDUsage_GD_Y] = PadButtonLeftStickY; + device->axisDialect_[kHIDUsage_GD_Rx] = PadButtonRightStickX; + device->axisDialect_[kHIDUsage_GD_Ry] = PadButtonRightStickY; + device->axisDialect_[kHIDUsage_GD_Z] = PadButtonAxis4; + device->axisDialect_[kHIDUsage_GD_Rz] = PadButtonAxis5; + device->buttonDialect_[0x0a] = PadButtonSelect; + device->buttonDialect_[0x07] = PadButtonL3; + device->buttonDialect_[0x08] = PadButtonR3; + device->buttonDialect_[0x09] = PadButtonStart; + device->buttonDialect_[0x0c] = PadButtonUp; + device->buttonDialect_[0x0f] = PadButtonRight; + device->buttonDialect_[0x0d] = PadButtonDown; + device->buttonDialect_[0x0e] = PadButtonLeft; + device->buttonDialect_[0x05] = PadButtonL1; + device->buttonDialect_[0x06] = PadButtonR1; + device->buttonDialect_[0x04] = PadButtonY; + device->buttonDialect_[0x02] = PadButtonB; + device->buttonDialect_[0x01] = PadButtonA; + device->buttonDialect_[0x03] = PadButtonX; + device->buttonDialect_[0x0b] = PadButtonHome; + } +} + +static void OnDeviceRemoved(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef) +{ + InputDevicePadImplMac* device = reinterpret_cast(inContext); + GAINPUT_ASSERT(device); + device->deviceState_ = InputDevice::DS_UNAVAILABLE; + if (device->index_ < kMaxPads) + { + usedPadIndices_[device->index_] = true; + } +} + +} + +InputDevicePadImplMac::InputDevicePadImplMac(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) : + buttonDialect_(manager.GetAllocator()), + axisDialect_(manager.GetAllocator()), + minAxis_(-FLT_MAX), + maxAxis_(FLT_MAX), + minTriggerAxis_(-FLT_MAX), + maxTriggerAxis_(FLT_MAX), + manager_(manager), + device_(device), + index_(index), + state_(state), + previousState_(previousState), + nextState_(manager.GetAllocator(), PadButtonCount_ + PadButtonAxisCount_), + deviceState_(InputDevice::DS_UNAVAILABLE), + ioManager_(0) +{ + IOHIDManagerRef ioManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone); + + if (CFGetTypeID(ioManager) != IOHIDManagerGetTypeID()) + { + return; + } + + ioManager_ = ioManager; + + static const unsigned kKeyCount = 2; + + CFStringRef keys[kKeyCount] = { + CFSTR(kIOHIDDeviceUsagePageKey), + CFSTR(kIOHIDDeviceUsageKey), + }; + + int usagePage = kHIDPage_GenericDesktop; + int usage = kHIDUsage_GD_GamePad; + CFNumberRef values[kKeyCount] = { + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usagePage), + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usage), + }; + + CFMutableArrayRef matchingArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + + CFDictionaryRef matchingDict = CFDictionaryCreate(kCFAllocatorDefault, + (const void **) keys, (const void **) values, kKeyCount, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFArrayAppendValue(matchingArray, matchingDict); + CFRelease(matchingDict); + CFRelease(values[1]); + + usage = kHIDUsage_GD_MultiAxisController; + values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usage); + + matchingDict = CFDictionaryCreate(kCFAllocatorDefault, + (const void **) keys, (const void **) values, kKeyCount, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFArrayAppendValue(matchingArray, matchingDict); + CFRelease(matchingDict); + CFRelease(values[1]); + + usage = kHIDUsage_GD_Joystick; + values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usage); + + matchingDict = CFDictionaryCreate(kCFAllocatorDefault, + (const void **) keys, (const void **) values, kKeyCount, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFArrayAppendValue(matchingArray, matchingDict); + CFRelease(matchingDict); + + for (unsigned i = 0; i < kKeyCount; ++i) + { + CFRelease(keys[i]); + CFRelease(values[i]); + } + + IOHIDManagerSetDeviceMatchingMultiple(ioManager, matchingArray); + CFRelease(matchingArray); + + IOHIDManagerRegisterDeviceMatchingCallback(ioManager, OnDeviceConnected, this); + IOHIDManagerRegisterDeviceRemovalCallback(ioManager, OnDeviceRemoved, this); + IOHIDManagerRegisterInputValueCallback(ioManager, OnDeviceInput, this); + + IOHIDManagerOpen(ioManager, kIOHIDOptionsTypeNone); + + IOHIDManagerScheduleWithRunLoop(ioManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); +} + +InputDevicePadImplMac::~InputDevicePadImplMac() +{ + IOHIDManagerRef ioManager = reinterpret_cast(ioManager_); + IOHIDManagerUnscheduleFromRunLoop(ioManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerClose(ioManager, 0); + CFRelease(ioManager); +} + +void InputDevicePadImplMac::Update(InputDeltaState* delta) +{ + delta_ = delta; + state_ = nextState_; +} + +bool InputDevicePadImplMac::IsValidButton(DeviceButtonId deviceButton) const +{ + if (buttonDialect_.empty()) + { + return deviceButton < PadButtonMax_; + } + + for (HashMap::const_iterator it = buttonDialect_.begin(); + it != buttonDialect_.end(); + ++it) + { + if (it->second == deviceButton) + { + return true; + } + } + + for (HashMap::const_iterator it = axisDialect_.begin(); + it != axisDialect_.end(); + ++it) + { + if (it->second == deviceButton) + { + return true; + } + } + + return false; +} + +} + +#endif + diff --git a/lib/source/gainput/pad/GainputInputDevicePadMac.h b/lib/source/gainput/pad/GainputInputDevicePadMac.h new file mode 100644 index 00000000..01dc58a3 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadMac.h @@ -0,0 +1,57 @@ + +#ifndef GAINPUTINPUTDEVICEPADMAC_H_ +#define GAINPUTINPUTDEVICEPADMAC_H_ + + +namespace gainput +{ + +class InputDevicePadImplMac : public InputDevicePadImpl +{ +public: + InputDevicePadImplMac(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState); + ~InputDevicePadImplMac(); + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta); + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + bool IsValidButton(DeviceButtonId deviceButton) const; + + bool Vibrate(float leftMotor, float rightMotor) + { + return false; + } + + HashMap buttonDialect_; + HashMap axisDialect_; + float minAxis_; + float maxAxis_; + float minTriggerAxis_; + float maxTriggerAxis_; + InputManager& manager_; + InputDevice& device_; + unsigned index_; + InputState& state_; + InputState& previousState_; + InputState nextState_; + InputDeltaState* delta_; + InputDevice::DeviceState deviceState_; + + void* ioManager_; + +private: +}; + +} + +#endif + diff --git a/lib/source/gainput/pad/GainputInputDevicePadNull.h b/lib/source/gainput/pad/GainputInputDevicePadNull.h new file mode 100644 index 00000000..13b70f39 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadNull.h @@ -0,0 +1,43 @@ + +#ifndef GAINPUTINPUTDEVICEPADNULL_H_ +#define GAINPUTINPUTDEVICEPADNULL_H_ + + +namespace gainput +{ + +class InputDevicePadImplNull : public InputDevicePadImpl +{ +public: + InputDevicePadImplNull(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_NULL; + } + + void Update(InputDeltaState* /*delta*/) + { + } + + InputDevice::DeviceState GetState() const + { + return InputDevice::DS_OK; + } + + bool IsValidButton(DeviceButtonId /*deviceButton*/) const + { + return false; + } + + bool Vibrate(float /*leftMotor*/, float /*rightMotor*/) + { + return false; + } +}; + +} + +#endif diff --git a/lib/source/gainput/pad/GainputInputDevicePadWin.h b/lib/source/gainput/pad/GainputInputDevicePadWin.h new file mode 100644 index 00000000..967eda09 --- /dev/null +++ b/lib/source/gainput/pad/GainputInputDevicePadWin.h @@ -0,0 +1,165 @@ + +#ifndef GAINPUTINPUTDEVICEPADWIN_H_ +#define GAINPUTINPUTDEVICEPADWIN_H_ + +// Cf. http://msdn.microsoft.com/en-us/library/windows/desktop/ee417005%28v=vs.85%29.aspx + +#include "../GainputWindows.h" +#include + +namespace gainput +{ + + +const float MaxTriggerValue = 255.0f; +const float MaxAxisValue = 32767.0f; +const float MaxNegativeAxisValue = 32768.0f; +const float MaxMotorSpeed = 65535.0f; + + +class InputDevicePadImplWin : public InputDevicePadImpl +{ +public: + InputDevicePadImplWin(InputManager& manager, InputDevice& device, unsigned index, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(state), + previousState_(previousState), + deviceState_(InputDevice::DS_UNAVAILABLE), + lastPacketNumber_(-1), + hasBattery_(false) + { + padIndex_ = index; + GAINPUT_ASSERT(padIndex_ < MaxPadCount); + +#if 0 + XINPUT_BATTERY_INFORMATION xbattery; + DWORD result = XInputGetBatteryInformation(padIndex, BATTERY_DEVTYPE_GAMEPAD, &xbattery); + if (result == ERROR_SUCCESS) + { + hasBattery = (xbattery.BatteryType == BATTERY_TYPE_ALKALINE + || xbattery.BatteryType == BATTERY_TYPE_NIMH); + } +#endif + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + XINPUT_STATE xstate; + DWORD result = XInputGetState(padIndex_, &xstate); + + if (result != ERROR_SUCCESS) + { + deviceState_ = InputDevice::DS_UNAVAILABLE; + return; + } + + deviceState_ = InputDevice::DS_OK; + +#if 0 + if (hasBattery) + { + XINPUT_BATTERY_INFORMATION xbattery; + result = XInputGetBatteryInformation(padIndex, BATTERY_DEVTYPE_GAMEPAD, &xbattery); + if (result == ERROR_SUCCESS) + { + if (xbattery.BatteryType == BATTERY_TYPE_ALKALINE + || xbattery.BatteryType == BATTERY_TYPE_NIMH) + { + if (xbattery.BatteryLevel == BATTERY_LEVEL_EMPTY + || xbattery.BatteryLevel == BATTERY_LEVEL_LOW) + { + deviceState = InputDevice::DS_LOW_BATTERY; + } + } + } + } +#endif + + if (xstate.dwPacketNumber == lastPacketNumber_) + { + // Not changed + return; + } + + lastPacketNumber_ = xstate.dwPacketNumber; + + HandleButton(device_, state_, delta, PadButtonUp, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0); + HandleButton(device_, state_, delta, PadButtonDown, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0); + HandleButton(device_, state_, delta, PadButtonLeft, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0); + HandleButton(device_, state_, delta, PadButtonRight, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0); + HandleButton(device_, state_, delta, PadButtonA, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0); + HandleButton(device_, state_, delta, PadButtonB, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_B) != 0); + HandleButton(device_, state_, delta, PadButtonX, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_X) != 0); + HandleButton(device_, state_, delta, PadButtonY, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y) != 0); + HandleButton(device_, state_, delta, PadButtonStart, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_START) != 0); + HandleButton(device_, state_, delta, PadButtonSelect, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) != 0); + HandleButton(device_, state_, delta, PadButtonL3, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0); + HandleButton(device_, state_, delta, PadButtonR3, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0); + HandleButton(device_, state_, delta, PadButtonL1, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0); + HandleButton(device_, state_, delta, PadButtonR1, (xstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0); + HandleButton(device_, state_, delta, PadButtonL2, xstate.Gamepad.bLeftTrigger != 0); + HandleButton(device_, state_, delta, PadButtonR2, xstate.Gamepad.bRightTrigger != 0); + + HandleAxis(device_, state_, delta, PadButtonAxis4, float(xstate.Gamepad.bLeftTrigger)/MaxTriggerValue); + HandleAxis(device_, state_, delta, PadButtonAxis5, float(xstate.Gamepad.bRightTrigger)/MaxTriggerValue); + HandleAxis(device_, state_, delta, PadButtonLeftStickX, GetAxisValue(xstate.Gamepad.sThumbLX)); + HandleAxis(device_, state_, delta, PadButtonLeftStickY, GetAxisValue(xstate.Gamepad.sThumbLY)); + HandleAxis(device_, state_, delta, PadButtonRightStickX, GetAxisValue(xstate.Gamepad.sThumbRX)); + HandleAxis(device_, state_, delta, PadButtonRightStickY, GetAxisValue(xstate.Gamepad.sThumbRY)); + } + + InputDevice::DeviceState GetState() const + { + return deviceState_; + } + + bool IsValidButton(DeviceButtonId deviceButton) const + { + return deviceButton < PadButtonAxis4 || (deviceButton >= PadButtonStart && deviceButton <= PadButtonR3); + } + + bool Vibrate(float leftMotor, float rightMotor) + { + GAINPUT_ASSERT(leftMotor >= 0.0f && leftMotor <= 1.0f); + GAINPUT_ASSERT(rightMotor >= 0.0f && rightMotor <= 1.0f); + XINPUT_VIBRATION xvibration; + xvibration.wLeftMotorSpeed = static_cast(leftMotor*MaxMotorSpeed); + xvibration.wRightMotorSpeed = static_cast(rightMotor*MaxMotorSpeed); + DWORD result = XInputSetState(padIndex_, &xvibration); + return result == ERROR_SUCCESS; + } + +private: + InputManager& manager_; + InputDevice& device_; + InputState& state_; + InputState& previousState_; + InputDevice::DeviceState deviceState_; + unsigned padIndex_; + DWORD lastPacketNumber_; + bool hasBattery_; + + static float GetAxisValue(SHORT value) + { + if (value < 0) + { + return float(value) / MaxNegativeAxisValue; + } + else + { + return float(value) / MaxAxisValue; + } + } + +}; + +} + +#endif + diff --git a/lib/source/gainput/recorder/GainputInputPlayer.cpp b/lib/source/gainput/recorder/GainputInputPlayer.cpp new file mode 100644 index 00000000..141a5c9c --- /dev/null +++ b/lib/source/gainput/recorder/GainputInputPlayer.cpp @@ -0,0 +1,111 @@ + +#include + +#ifdef GAINPUT_ENABLE_RECORDER + +#include +#include +#include + + +namespace gainput +{ + +InputPlayer::InputPlayer(InputManager& manager, InputRecording* recording) : + manager_(manager), + isPlaying_(false), + recording_(recording), + startTime_(0), + devicesToReset_(manager.GetAllocator()) +{ + playingModifierId_ = manager_.AddDeviceStateModifier(this); +} + +InputPlayer::~InputPlayer() +{ + manager_.RemoveDeviceStateModifier(playingModifierId_); +} + +void +InputPlayer::Update(InputDeltaState* delta) +{ + if (!isPlaying_) + { + return; + } + + uint64_t now = manager_.GetTime(); + GAINPUT_ASSERT(now >= startTime_); + now -= startTime_; + + RecordedDeviceButtonChange change; + while (recording_->GetNextChange(now, change)) + { + InputDevice* device = manager_.GetDevice(change.deviceId); + + GAINPUT_ASSERT(device); + GAINPUT_ASSERT(device->IsValidButtonId(change.buttonId)); + GAINPUT_ASSERT(device->GetInputState()); + GAINPUT_ASSERT(device->GetPreviousInputState()); + + if (!device->IsSynced()) + { + device->SetSynced(true); + devicesToReset_.push_back(change.deviceId); + } + + if (device->GetButtonType(change.buttonId) == BT_BOOL) + { + HandleButton(*device, *device->GetInputState(), delta, change.buttonId, change.b); + } + else + { + HandleAxis(*device, *device->GetInputState(), delta, change.buttonId, change.f); + } + } + + if (now >= recording_->GetDuration()) + { +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("GAINPUT: Recording is over. Stopping playback.\n"); +#endif + Stop(); + } +} + +void +InputPlayer::SetRecording(InputRecording* recording) +{ + GAINPUT_ASSERT(!isPlaying_); + recording_ = recording; +} + +void +InputPlayer::Start() +{ + GAINPUT_ASSERT(recording_); + isPlaying_ = true; + startTime_ = manager_.GetTime(); + recording_->Reset(); +} + +void +InputPlayer::Stop() +{ + isPlaying_ = false; + + for (Array::const_iterator it = devicesToReset_.begin(); + it != devicesToReset_.end(); + ++it) + { + InputDevice* device = manager_.GetDevice(*it); + GAINPUT_ASSERT(device); + device->SetSynced(false); + } + devicesToReset_.clear(); +} + +} + +#endif + diff --git a/lib/source/gainput/recorder/GainputInputRecorder.cpp b/lib/source/gainput/recorder/GainputInputRecorder.cpp new file mode 100644 index 00000000..aa29551d --- /dev/null +++ b/lib/source/gainput/recorder/GainputInputRecorder.cpp @@ -0,0 +1,146 @@ + +#include + +#ifdef GAINPUT_ENABLE_RECORDER + +namespace gainput +{ + +namespace +{ + class RecordingListener : public InputListener + { + public: + RecordingListener(InputManager& manager, InputRecorder& recorder) : + manager_(manager), + recorder_(recorder) + { } + + bool OnDeviceButtonBool(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, bool /*oldValue*/, bool newValue) + { + if (!recorder_.IsDeviceToRecord(deviceId)) + { + return true; + } + + InputRecording* recording = recorder_.GetRecording(); + GAINPUT_ASSERT(recording); + GAINPUT_ASSERT(manager_.GetTime() >= recorder_.GetStartTime()); + const uint64_t time = manager_.GetTime() - recorder_.GetStartTime(); + recording->AddChange(time, deviceId, deviceButton, newValue); + + return true; + } + + bool OnDeviceButtonFloat(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, float /*oldValue*/, float newValue) + { + if (!recorder_.IsDeviceToRecord(deviceId)) + { + return true; + } + + InputRecording* recording = recorder_.GetRecording(); + GAINPUT_ASSERT(recording); + GAINPUT_ASSERT(manager_.GetTime() >= recorder_.GetStartTime()); + const uint64_t time = manager_.GetTime() - recorder_.GetStartTime(); + recording->AddChange(time, deviceId, deviceButton, newValue); + + return true; + } + + private: + InputManager& manager_; + InputRecorder& recorder_; + + }; +} + + +InputRecorder::InputRecorder(InputManager& manager) : + manager_(manager), + isRecording_(false), + recordingListener_(0), + recording_(0), + startTime_(0), + recordedDevices_(manager.GetAllocator()) +{ +} + +InputRecorder::~InputRecorder() +{ + Stop(); + + if (recording_) + { + manager_.GetAllocator().Delete(recording_); + } +} + +void +InputRecorder::Start() +{ + isRecording_ = true; + + if (recording_) + { + recording_->Clear(); + } + else + { + recording_ = manager_.GetAllocator().New(manager_.GetAllocator()); + } + + startTime_ = manager_.GetTime(); + + recordingListener_ = manager_.GetAllocator().New(manager_, *this); + recordingListenerId_ = manager_.AddListener(recordingListener_); + + // Record the initial state + for (InputManager::iterator it = manager_.begin(); + it != manager_.end(); + ++it) + { + InputDevice* device = it->second; + const DeviceId deviceId = device->GetDeviceId(); + + if (!IsDeviceToRecord(deviceId)) + { + continue; + } + + GAINPUT_ASSERT(device->GetInputState()); + for (DeviceButtonId buttonId = 0; buttonId < device->GetInputState()->GetButtonCount(); ++buttonId) + { + if (device->IsValidButtonId(buttonId)) + { + if (device->GetButtonType(buttonId) == BT_BOOL) + { + recording_->AddChange(0, deviceId, buttonId, device->GetBool(buttonId)); + } + else + { + recording_->AddChange(0, deviceId, buttonId, device->GetFloat(buttonId)); + } + } + } + } +} + +void +InputRecorder::Stop() +{ + if (!isRecording_) + { + return; + } + + isRecording_ = false; + manager_.RemoveListener(recordingListenerId_); + manager_.GetAllocator().Delete(recordingListener_); + recordingListener_ = 0; +} + +} + +#endif + diff --git a/lib/source/gainput/recorder/GainputInputRecording.cpp b/lib/source/gainput/recorder/GainputInputRecording.cpp new file mode 100644 index 00000000..18d0bba2 --- /dev/null +++ b/lib/source/gainput/recorder/GainputInputRecording.cpp @@ -0,0 +1,179 @@ + +#include + +#ifdef GAINPUT_ENABLE_RECORDER +#include "../dev/GainputStream.h" +#include "../dev/GainputMemoryStream.h" + +const static unsigned SerializationVersion = 0x1; + +namespace gainput +{ + +InputRecording::InputRecording(Allocator& allocator) : + changes_(allocator), + position_(0) +{ +} + +InputRecording::InputRecording(InputManager& manager, void* data, size_t size, Allocator& allocator) : + changes_(allocator), + position_(0) +{ + GAINPUT_ASSERT(data); + GAINPUT_ASSERT(size > 0); + + Stream* stream = allocator.New(data, size, size); + + uint32_t t; + uint8_t deviceType; + uint8_t deviceIndex; + stream->Read(t); + GAINPUT_ASSERT(t == SerializationVersion); + + while (!stream->IsEof()) + { + RecordedDeviceButtonChange change; + + stream->Read(t); + change.time = t; + stream->Read(deviceType); + stream->Read(deviceIndex); + change.deviceId = manager.FindDeviceId(InputDevice::DeviceType(deviceType), deviceIndex); + GAINPUT_ASSERT(change.deviceId != InvalidDeviceId); + + stream->Read(t); + change.buttonId = t; + + const InputDevice* device = manager.GetDevice(change.deviceId); + GAINPUT_ASSERT(device); + + if (device->GetButtonType(change.buttonId) == BT_BOOL) + { + uint8_t value; + stream->Read(value); + change.b = (value != 0); + GAINPUT_ASSERT(sizeof(float) >= sizeof(uint8_t)); + for (size_t i = 0; i < sizeof(float)-sizeof(uint8_t); ++i) + { + uint8_t t8; + stream->Read(t8); + } + } + else + { + float value; + stream->Read(value); + change.f = value; + } + + changes_.push_back(change); + } + + GAINPUT_ASSERT(stream->GetLeft() == 0); + allocator.Delete(stream); +} + +void +InputRecording::AddChange(uint64_t time, DeviceId deviceId, DeviceButtonId buttonId, bool value) +{ + RecordedDeviceButtonChange change; + change.time = time; + change.deviceId = deviceId; + change.buttonId = buttonId; + change.b = value; + changes_.push_back(change); +} + +void +InputRecording::AddChange(uint64_t time, DeviceId deviceId, DeviceButtonId buttonId, float value) +{ + RecordedDeviceButtonChange change; + change.time = time; + change.deviceId = deviceId; + change.buttonId = buttonId; + change.f = value; + changes_.push_back(change); +} + +void +InputRecording::Clear() +{ + changes_.clear(); +} + +bool +InputRecording::GetNextChange(uint64_t time, RecordedDeviceButtonChange& outChange) +{ + if (position_ >= changes_.size()) + { + return false; + } + + if (changes_[position_].time > time) + { + return false; + } + + outChange = changes_[position_]; + ++position_; + + return true; +} + +uint64_t +InputRecording::GetDuration() const +{ + return changes_.empty() ? 0 : changes_[changes_.size() - 1].time; +} + +size_t +InputRecording::GetSerializedSize() const +{ + return ( + sizeof(uint32_t) + + changes_.size() * (sizeof(uint32_t) + 2*sizeof(uint8_t) + sizeof(uint32_t) + sizeof(float)) + ); +} + +void +InputRecording::GetSerialized(InputManager& manager, void* data) const +{ + Stream* stream = manager.GetAllocator().New(data, 0, GetSerializedSize()); + + stream->Write(uint32_t(SerializationVersion)); + + for (Array::const_iterator it = changes_.begin(); + it != changes_.end(); + ++it) + { + const InputDevice* device = manager.GetDevice(it->deviceId); + GAINPUT_ASSERT(device); + stream->Write(uint32_t(it->time)); + stream->Write(uint8_t(device->GetType())); + stream->Write(uint8_t(device->GetIndex())); + stream->Write(uint32_t(it->buttonId)); + if (device->GetButtonType(it->buttonId) == BT_BOOL) + { + stream->Write(uint8_t(it->b)); + GAINPUT_ASSERT(sizeof(float) >= sizeof(uint8_t)); + for (size_t i = 0; i < sizeof(float)-sizeof(uint8_t); ++i) + { + stream->Write(uint8_t(0)); + } + } + else + { + stream->Write(float(it->f)); + } + } + + GAINPUT_ASSERT(stream->GetLeft() == 0); + + manager.GetAllocator().Delete(stream); +} + +} + +#endif + diff --git a/lib/source/gainput/touch/GainputInputDeviceTouch.cpp b/lib/source/gainput/touch/GainputInputDeviceTouch.cpp new file mode 100644 index 00000000..4aecbbee --- /dev/null +++ b/lib/source/gainput/touch/GainputInputDeviceTouch.cpp @@ -0,0 +1,157 @@ + +#include +#include + +#include "GainputInputDeviceTouchImpl.h" +#include "GainputTouchInfo.h" +#include +#include +#include + +#include "GainputInputDeviceTouchNull.h" + +#if defined(GAINPUT_PLATFORM_ANDROID) + #include "GainputInputDeviceTouchAndroid.h" +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS) + #include "GainputInputDeviceTouchIos.h" +#endif + +namespace gainput +{ + +InputDeviceTouch::InputDeviceTouch(InputManager& manager, DeviceId device, unsigned index, DeviceVariant variant) : + InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_TOUCH) : 0), + impl_(0) +{ + state_ = manager.GetAllocator().New(manager.GetAllocator(), TouchPointCount*TouchDataElems); + GAINPUT_ASSERT(state_); + previousState_ = manager.GetAllocator().New(manager.GetAllocator(), TouchPointCount*TouchDataElems); + GAINPUT_ASSERT(previousState_); + +#if defined(GAINPUT_PLATFORM_ANDROID) + if (variant != DV_NULL) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } +#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS) + if (variant != DV_NULL) + { + impl_ = manager.GetAllocator().New(manager, *this, *state_, *previousState_); + } +#endif + + if (!impl_) + { + impl_ = manager.GetAllocator().New(manager, *this); + } + GAINPUT_ASSERT(impl_); +} + +InputDeviceTouch::~InputDeviceTouch() +{ + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); + manager_.GetAllocator().Delete(impl_); +} + +bool +InputDeviceTouch::IsValidButtonId(DeviceButtonId deviceButton) const +{ + if (deviceButton == Touch0Pressure + || deviceButton == Touch1Pressure + || deviceButton == Touch2Pressure + || deviceButton == Touch3Pressure + || deviceButton == Touch4Pressure + || deviceButton == Touch5Pressure + || deviceButton == Touch6Pressure + || deviceButton == Touch7Pressure + ) + { + return impl_->SupportsPressure(); + } + return deviceButton < TouchCount_; +} + +void +InputDeviceTouch::InternalUpdate(InputDeltaState* delta) +{ + impl_->Update(delta); + + if ((manager_.IsDebugRenderingEnabled() || IsDebugRenderingEnabled()) + && manager_.GetDebugRenderer()) + { + DebugRenderer* debugRenderer = manager_.GetDebugRenderer(); + InputState* state = GetInputState(); + + for (unsigned i = 0; i < TouchPointCount; ++i) + { + if (state->GetBool(Touch0Down + i*4)) + { + const float x = state->GetFloat(Touch0X + i*4); + const float y = state->GetFloat(Touch0Y + i*4); + debugRenderer->DrawCircle(x, y, 0.03f); + } + } + } +} + +InputDevice::DeviceState +InputDeviceTouch::InternalGetState() const +{ + return impl_->GetState(); +} + +InputDevice::DeviceVariant +InputDeviceTouch::GetVariant() const +{ + return impl_->GetVariant(); +} + +size_t +InputDeviceTouch::GetAnyButtonDown(DeviceButtonSpec* outButtons, size_t maxButtonCount) const +{ + GAINPUT_ASSERT(outButtons); + GAINPUT_ASSERT(maxButtonCount > 0); + return CheckAllButtonsDown(outButtons, maxButtonCount, Touch0Down, TouchCount_); +} + +size_t +InputDeviceTouch::GetButtonName(DeviceButtonId deviceButton, char* buffer, size_t bufferLength) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + GAINPUT_ASSERT(buffer); + GAINPUT_ASSERT(bufferLength > 0); + strncpy(buffer, deviceButtonInfos[deviceButton].name, bufferLength); + buffer[bufferLength-1] = 0; + const size_t nameLen = strlen(deviceButtonInfos[deviceButton].name); + return nameLen >= bufferLength ? bufferLength : nameLen+1; +} + +ButtonType +InputDeviceTouch::GetButtonType(DeviceButtonId deviceButton) const +{ + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + return deviceButtonInfos[deviceButton].type; +} + +DeviceButtonId +InputDeviceTouch::GetButtonByName(const char* name) const +{ + GAINPUT_ASSERT(name); + for (unsigned i = 0; i < TouchPointCount*TouchDataElems; ++i) + { + if (strcmp(name, deviceButtonInfos[i].name) == 0) + { + return DeviceButtonId(i); + } + } + return InvalidDeviceButtonId; +} + +InputState* +InputDeviceTouch::GetNextInputState() +{ + return impl_->GetNextInputState(); +} + +} diff --git a/lib/source/gainput/touch/GainputInputDeviceTouchAndroid.h b/lib/source/gainput/touch/GainputInputDeviceTouchAndroid.h new file mode 100644 index 00000000..cc10f037 --- /dev/null +++ b/lib/source/gainput/touch/GainputInputDeviceTouchAndroid.h @@ -0,0 +1,99 @@ + +#ifndef GAINPUTINPUTDEVICETOUCHANDROID_H_ +#define GAINPUTINPUTDEVICETOUCHANDROID_H_ + +#include + +#include "GainputInputDeviceTouchImpl.h" +#include "GainputTouchInfo.h" +#include + +namespace gainput +{ + +class InputDeviceTouchImplAndroid : public InputDeviceTouchImpl +{ +public: + InputDeviceTouchImplAndroid(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), TouchPointCount*TouchDataElems), + delta_(0) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } + + int32_t HandleInput(AInputEvent* event) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + GAINPUT_ASSERT(event); + + if (AInputEvent_getType(event) != AINPUT_EVENT_TYPE_MOTION) + { + return 0; + } + + for (unsigned i = 0; i < AMotionEvent_getPointerCount(event) && i < TouchPointCount; ++i) + { + GAINPUT_ASSERT(i < TouchPointCount); + const float x = AMotionEvent_getX(event, i); + const float y = AMotionEvent_getY(event, i); + const int32_t w = manager_.GetDisplayWidth(); + const int32_t h = manager_.GetDisplayHeight(); + HandleFloat(Touch0X + i*TouchDataElems, x/float(w)); + HandleFloat(Touch0Y + i*TouchDataElems, y/float(h)); + const int motionAction = AMotionEvent_getAction(event); + const bool down = (motionAction == AMOTION_EVENT_ACTION_DOWN || motionAction == AMOTION_EVENT_ACTION_MOVE); + HandleBool(Touch0Down + i*TouchDataElems, down); + HandleFloat(Touch0Pressure + i*TouchDataElems, AMotionEvent_getPressure(event, i)); +#ifdef GAINPUT_DEBUG + GAINPUT_LOG("Touch %i) x: %f, y: %f, w: %i, h: %i, action: %d\n", i, x, y, w, h, motionAction); +#endif + } + + return 1; + } + + InputState* GetNextInputState() + { + return &nextState_; + } + +private: + InputManager& manager_; + InputDevice& device_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; + + void HandleBool(DeviceButtonId buttonId, bool value) + { + HandleButton(device_, nextState_, delta_, buttonId, value); + } + + void HandleFloat(DeviceButtonId buttonId, float value) + { + HandleAxis(device_, nextState_, delta_, buttonId, value); + } +}; + +} + +#endif + diff --git a/lib/source/gainput/touch/GainputInputDeviceTouchImpl.h b/lib/source/gainput/touch/GainputInputDeviceTouchImpl.h new file mode 100644 index 00000000..999d7b69 --- /dev/null +++ b/lib/source/gainput/touch/GainputInputDeviceTouchImpl.h @@ -0,0 +1,22 @@ + +#ifndef GAINPUTINPUTDEVICETOUCHIMPL_H_ +#define GAINPUTINPUTDEVICETOUCHIMPL_H_ + +namespace gainput +{ + +class InputDeviceTouchImpl +{ +public: + virtual ~InputDeviceTouchImpl() { } + virtual InputDevice::DeviceVariant GetVariant() const = 0; + virtual InputDevice::DeviceState GetState() const = 0; + virtual void Update(InputDeltaState* delta) = 0; + virtual bool SupportsPressure() const { return false; } + virtual InputState* GetNextInputState() { return 0; } +}; + +} + +#endif + diff --git a/lib/source/gainput/touch/GainputInputDeviceTouchIos.h b/lib/source/gainput/touch/GainputInputDeviceTouchIos.h new file mode 100644 index 00000000..a207f029 --- /dev/null +++ b/lib/source/gainput/touch/GainputInputDeviceTouchIos.h @@ -0,0 +1,149 @@ + +#ifndef GAINPUTINPUTDEVICETOUCHANDROID_H_ +#define GAINPUTINPUTDEVICETOUCHANDROID_H_ + +#include "GainputInputDeviceTouchImpl.h" +#include "GainputTouchInfo.h" + +#include +#include +#include + +namespace gainput +{ + +class InputDeviceTouchImplIos : public InputDeviceTouchImpl +{ +public: + InputDeviceTouchImplIos(InputManager& manager, InputDevice& device, InputState& state, InputState& previousState) : + manager_(manager), + device_(device), + state_(&state), + previousState_(&previousState), + nextState_(manager.GetAllocator(), TouchPointCount*TouchDataElems), + delta_(0), + touches_(manager.GetAllocator()), + supportsPressure_(false) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_STANDARD; + } + + void Update(InputDeltaState* delta) + { + delta_ = delta; + *state_ = nextState_; + } + + InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } + + bool SupportsPressure() const + { + return supportsPressure_; + } + + void SetSupportsPressure(bool supports) + { + supportsPressure_ = supports; + } + + void HandleTouch(void* id, float x, float y, float z = 0.f) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + int touchIdx = -1; + for (unsigned i = 0; i < touches_.size(); ++i) + { + if (touches_[i] == static_cast(id)) + { + touchIdx = i; + break; + } + } + + if (touchIdx == -1) + { + for (unsigned i = 0; i < touches_.size(); ++i) + { + if (touches_[i] == 0) + { + touches_[i] = static_cast(id); + touchIdx = i; + break; + } + } + } + + if (touchIdx == -1) + { + touchIdx = static_cast(touches_.size()); + touches_.push_back(static_cast(id)); + } + + HandleBool(gainput::Touch0Down + touchIdx*4, true); + HandleFloat(gainput::Touch0X + touchIdx*4, x); + HandleFloat(gainput::Touch0Y + touchIdx*4, y); + HandleFloat(gainput::Touch0Pressure + touchIdx*4, z); + } + + void HandleTouchEnd(void* id, float x, float y, float z = 0.f) + { + GAINPUT_ASSERT(state_); + GAINPUT_ASSERT(previousState_); + + int touchIdx = -1; + for (unsigned i = 0; i < touches_.size(); ++i) + { + if (touches_[i] == static_cast(id)) + { + touchIdx = i; + break; + } + } + + GAINPUT_ASSERT(touchIdx != -1); + if (touchIdx == -1) + { + return; + } + + touches_[touchIdx] = 0; + + HandleBool(gainput::Touch0Down + touchIdx*4, false); + HandleFloat(gainput::Touch0X + touchIdx*4, x); + HandleFloat(gainput::Touch0Y + touchIdx*4, y); + HandleFloat(gainput::Touch0Pressure + touchIdx*4, z); + } + +private: + InputManager& manager_; + InputDevice& device_; + InputState* state_; + InputState* previousState_; + InputState nextState_; + InputDeltaState* delta_; + + typedef gainput::Array< void* > TouchList; + TouchList touches_; + + bool supportsPressure_; + + void HandleBool(DeviceButtonId buttonId, bool value) + { + manager_.EnqueueConcurrentChange(device_, nextState_, delta_, buttonId, value); + } + + void HandleFloat(DeviceButtonId buttonId, float value) + { + manager_.EnqueueConcurrentChange(device_, nextState_, delta_, buttonId, value); + } +}; + +} + +#endif + diff --git a/lib/source/gainput/touch/GainputInputDeviceTouchNull.h b/lib/source/gainput/touch/GainputInputDeviceTouchNull.h new file mode 100644 index 00000000..c95a4054 --- /dev/null +++ b/lib/source/gainput/touch/GainputInputDeviceTouchNull.h @@ -0,0 +1,30 @@ + +#ifndef GAINPUTINPUTDEVICETOUCHNULL_H_ +#define GAINPUTINPUTDEVICETOUCHNULL_H_ + +namespace gainput +{ + +class InputDeviceTouchImplNull : public InputDeviceTouchImpl +{ +public: + InputDeviceTouchImplNull(InputManager& manager, InputDevice& device) + { + } + + InputDevice::DeviceVariant GetVariant() const + { + return InputDevice::DV_NULL; + } + + void Update(InputDeltaState* /*delta*/) + { + } + + InputDevice::DeviceState GetState() const { return InputDevice::DS_OK; } +}; + +} + +#endif + diff --git a/lib/source/gainput/touch/GainputTouchInfo.h b/lib/source/gainput/touch/GainputTouchInfo.h new file mode 100644 index 00000000..956ce694 --- /dev/null +++ b/lib/source/gainput/touch/GainputTouchInfo.h @@ -0,0 +1,59 @@ + +#ifndef GAINPUTTOUCHINFO_H_ +#define GAINPUTTOUCHINFO_H_ + +namespace gainput +{ + +namespace +{ +struct DeviceButtonInfo +{ + ButtonType type; + const char* name; +}; + +DeviceButtonInfo const deviceButtonInfos[] = +{ + { BT_BOOL, "touch_0_down" }, + { BT_FLOAT, "touch_0_x" }, + { BT_FLOAT, "touch_0_y" }, + { BT_FLOAT, "touch_0_pressure" }, + { BT_BOOL, "touch_1_down" }, + { BT_FLOAT, "touch_1_x" }, + { BT_FLOAT, "touch_1_y" }, + { BT_FLOAT, "touch_1_pressure" }, + { BT_BOOL, "touch_2_down" }, + { BT_FLOAT, "touch_2_x" }, + { BT_FLOAT, "touch_2_y" }, + { BT_FLOAT, "touch_2_pressure" }, + { BT_BOOL, "touch_3_down" }, + { BT_FLOAT, "touch_3_x" }, + { BT_FLOAT, "touch_3_y" }, + { BT_FLOAT, "touch_3_pressure" }, + { BT_BOOL, "touch_4_down" }, + { BT_FLOAT, "touch_4_x" }, + { BT_FLOAT, "touch_4_y" }, + { BT_FLOAT, "touch_4_pressure" }, + { BT_BOOL, "touch_5_down" }, + { BT_FLOAT, "touch_5_x" }, + { BT_FLOAT, "touch_5_y" }, + { BT_FLOAT, "touch_5_pressure" }, + { BT_BOOL, "touch_6_down" }, + { BT_FLOAT, "touch_6_x" }, + { BT_FLOAT, "touch_6_y" }, + { BT_FLOAT, "touch_6_pressure" }, + { BT_BOOL, "touch_7_down" }, + { BT_FLOAT, "touch_7_x" }, + { BT_FLOAT, "touch_7_y" }, + { BT_FLOAT, "touch_7_pressure" } +}; +} + +const unsigned TouchPointCount = 8; +const unsigned TouchDataElems = 4; + +} + +#endif + diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt new file mode 100644 index 00000000..6c545ebb --- /dev/null +++ b/samples/CMakeLists.txt @@ -0,0 +1,20 @@ + +find_package(X11) + +if(!ANDROID) + find_package(OpenGL) +endif(!ANDROID) + +if(APPLE AND NOT IOS) + find_library(APPKIT AppKit) +else() + set(APPKIT "") +endif() + +add_subdirectory(basic) +add_subdirectory(dynamic) +add_subdirectory(gesture) +add_subdirectory(listener) +add_subdirectory(recording) +add_subdirectory(sync) + diff --git a/samples/android/AndroidManifest.xml b/samples/android/AndroidManifest.xml new file mode 100644 index 00000000..e4055584 --- /dev/null +++ b/samples/android/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/android/project.properties b/samples/android/project.properties new file mode 100644 index 00000000..a3ee5ab6 --- /dev/null +++ b/samples/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 diff --git a/samples/android/res/values/strings.xml b/samples/android/res/values/strings.xml new file mode 100644 index 00000000..e5037e5d --- /dev/null +++ b/samples/android/res/values/strings.xml @@ -0,0 +1,4 @@ + + + GainputSample + diff --git a/samples/basic/CMakeLists.txt b/samples/basic/CMakeLists.txt new file mode 100644 index 00000000..5b412e43 --- /dev/null +++ b/samples/basic/CMakeLists.txt @@ -0,0 +1,37 @@ + +project(basicsample) + +include_directories(../../lib/include/) + +file(GLOB_RECURSE sources *.cpp) + +if(APPLE) + file(GLOB_RECURSE mmsources *.mm) +endif() + +if(ANDROID) + add_library(basicsample SHARED ${sources}) +elseif(APPLE AND IOS) + set(imagessources + "${CMAKE_CURRENT_LIST_DIR}/../../extern/ios/Launch Screen.storyboard" + ) + add_executable(basicsample "MACOSX_BUNDLE" ${sources} ${mmsources} ${imagessources}) + set_target_properties( basicsample PROPERTIES + XCODE_ATTRIBUTE_INFOPLIST_FILE "${CMAKE_CURRENT_LIST_DIR}/../../extern/ios/Info.plist" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" + ) + target_link_libraries(basicsample ${UIKIT} ${QUARTZCORE}) +else() + add_executable(basicsample WIN32 ${sources} ${mmsources}) +endif() + +target_link_libraries(basicsample gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(basicsample X11 GL rt) +elseif(WIN32) + target_link_libraries(basicsample ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(basicsample ${FOUNDATION} ${IOKIT} ${APPKIT} ${GAME_CONTROLLER}) +endif() + diff --git a/samples/basic/basicsample_android.cpp b/samples/basic/basicsample_android.cpp new file mode 100644 index 00000000..4c938d70 --- /dev/null +++ b/samples/basic/basicsample_android.cpp @@ -0,0 +1,106 @@ + +#include + +#if defined(GAINPUT_PLATFORM_ANDROID) + +#include +#include + +#include +#include + +#include +#include +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "gainput", __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "gainput", __VA_ARGS__)) + + +// Define your user buttons +enum Button +{ + ButtonMenu, + ButtonConfirm +}; + + +static bool doExit = false; + +static int32_t MyHandleInput(struct android_app* app, AInputEvent* event) +{ + // Forward input events to Gainput + gainput::InputManager* inputManager = (gainput::InputManager*)app->userData; + static bool resSet = false; + if (!resSet) + { + inputManager->SetDisplaySize(ANativeWindow_getWidth(app->window), ANativeWindow_getHeight(app->window)); + resSet = true; + } + return inputManager->HandleInput(event); +} + +static void MyHandleCmd(struct android_app* app, int32_t cmd) +{ + switch (cmd) + { + case APP_CMD_SAVE_STATE: + break; + case APP_CMD_INIT_WINDOW: + break; + case APP_CMD_TERM_WINDOW: + doExit = true; + break; + case APP_CMD_LOST_FOCUS: + doExit = true; + break; + case APP_CMD_GAINED_FOCUS: + // bring back a certain functionality, like monitoring the accelerometer + break; + } +} + +void android_main(struct android_app* state) +{ + app_dummy(); + + // Set up Gainput + gainput::InputManager manager; + manager.CreateDevice(); + manager.CreateDevice(); + manager.CreateDevice(); + + // Make sure the app frowards input events to Gainput + state->userData = &manager; + state->onInputEvent = &MyHandleInput; + state->onAppCmd = &MyHandleCmd; + + while (!doExit) + { + // Update Gainput + manager.Update(); + + int ident; + int events; + struct android_poll_source* source; + + while (!doExit && (ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) + { + if (source != NULL) + { + source->process(state, source); + } + + if (state->destroyRequested != 0) + { + doExit = true; + } + } + } + + ANativeActivity_finish(state->activity); +} + + +#endif + diff --git a/samples/basic/basicsample_android_generic.cpp b/samples/basic/basicsample_android_generic.cpp new file mode 100644 index 00000000..1d0ecb93 --- /dev/null +++ b/samples/basic/basicsample_android_generic.cpp @@ -0,0 +1,77 @@ + +#include + +#if defined(GAINPUT_PLATFORM_ANDROID) + +#include +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "gainput", __VA_ARGS__)) + +// Define your user buttons +enum Button +{ + ButtonMenu, + ButtonConfirm, + MouseX, + MouseY +}; + +gainput::InputManager* manager; +gainput::InputMap* map; +gainput::DeviceId mouseId; +gainput::DeviceId keyboardId; +gainput::DeviceId padId; +gainput::DeviceId touchId; + + +extern "C" { +JNIEXPORT void JNICALL +Java_com_example_gainput_gainput_BasicActivity_nativeOnCreate(JNIEnv * /*env*/, jobject /*thiz*/) +{ + manager = new gainput::InputManager; + mouseId = manager->CreateDevice(); + keyboardId = manager->CreateDevice(); + padId = manager->CreateDevice(); + touchId = manager->CreateDevice(); + + map = new gainput::InputMap(*manager); + map->MapBool(ButtonMenu, keyboardId, gainput::KeyBack); + map->MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + map->MapBool(ButtonConfirm, padId, gainput::PadButtonA); + map->MapBool(ButtonConfirm, touchId, gainput::Touch0Down); + + map->MapFloat(MouseX, mouseId, gainput::MouseAxisX); + map->MapFloat(MouseY, mouseId, gainput::MouseAxisY); + map->MapFloat(MouseX, touchId, gainput::Touch0X); + map->MapFloat(MouseY, touchId, gainput::Touch0Y); +} + +JNIEXPORT void JNICALL +Java_com_example_gainput_gainput_BasicActivity_nativeOnUpdate(JNIEnv * /*env*/, jobject /*thiz*/) +{ + manager->Update(); + + if (map->GetBoolWasDown(ButtonMenu)) + { + LOGI("Open Menu!!"); + } + if (map->GetBoolWasDown(ButtonConfirm)) + { + LOGI("Confirmed!!"); + } + if (map->GetBool(ButtonConfirm)) + { + LOGI("LM down"); + } + + if (map->GetFloatDelta(MouseX) != 0.0f || map->GetFloatDelta(MouseY) != 0.0f) + { + LOGI("Mouse: %f, %f", map->GetFloat(MouseX), map->GetFloat(MouseY)); + } +} + +} + +#endif + diff --git a/samples/basic/basicsample_ios.mm b/samples/basic/basicsample_ios.mm new file mode 100644 index 00000000..a266575b --- /dev/null +++ b/samples/basic/basicsample_ios.mm @@ -0,0 +1,114 @@ + +#include + +#if defined(GAINPUT_PLATFORM_IOS) +#include + +#import + +#include + + +// Define your user buttons +enum Button +{ + ButtonMenu, + ButtonConfirm, + MouseX, + MouseY +}; + +gainput::InputManager* manager; +gainput::InputMap* map; +gainput::DeviceId mouseId; +gainput::DeviceId keyboardId; +gainput::DeviceId padId; +gainput::DeviceId touchId; + +@interface AppDelegate : UIResponder +@property (strong, nonatomic) UIWindow *window; +@end + +@implementation AppDelegate +CADisplayLink* displayLink; +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + manager = new gainput::InputManager; + mouseId = manager->CreateDevice(); + keyboardId = manager->CreateDevice(); + padId = manager->CreateDevice(); + touchId = manager->CreateDevice(); + + map = new gainput::InputMap(*manager); + map->MapBool(ButtonMenu, keyboardId, gainput::KeyEscape); + map->MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + map->MapBool(ButtonConfirm, padId, gainput::PadButtonA); + map->MapBool(ButtonConfirm, touchId, gainput::Touch0Down); + + map->MapFloat(MouseX, mouseId, gainput::MouseAxisX); + map->MapFloat(MouseY, mouseId, gainput::MouseAxisY); + map->MapFloat(MouseX, touchId, gainput::Touch0X); + map->MapFloat(MouseY, touchId, gainput::Touch0Y); + + + CGRect bounds = [[UIScreen mainScreen] bounds]; + self.window = [[[UIWindow alloc] init] autorelease]; + self.window.rootViewController = [[[UIViewController alloc] init] autorelease]; + self.window.backgroundColor = [UIColor blueColor]; + [self.window makeKeyAndVisible]; + [application setIdleTimerDisabled:TRUE]; + + UIView* gainputView = [[[GainputView alloc] initWithFrame:bounds inputManager:*manager] autorelease]; + self.window.rootViewController.view = gainputView; + + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)]; + NSAssert(displayLink, @"Failed to create display link."); + [displayLink setFrameInterval: 1]; + [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + return YES; +} + +- (void)update +{ + manager->Update(); + + // Check button states + if (map->GetBoolWasDown(ButtonConfirm)) + { + gainput::InputDevicePad* pad = static_cast(manager->GetDevice(padId)); + pad->Vibrate(1.0f, 0.0f); + } + if (map->GetBoolWasDown(ButtonMenu)) + { + gainput::InputDevicePad* pad = static_cast(manager->GetDevice(padId)); + pad->Vibrate(0.0f, 0.0f); + } + + if (map->GetBoolWasDown(ButtonMenu)) + { + std::cout << "Open Menu!!" << std::endl; + } + if (map->GetBoolWasDown(ButtonConfirm)) + { + std::cout << "Confirmed!!" << std::endl; + } + if (map->GetBool(ButtonConfirm)) + { + std::cout << "LM down" << std::endl; + } + + if (map->GetFloatDelta(MouseX) != 0.0f || map->GetFloatDelta(MouseY) != 0.0f) + { + std::cout << "Mouse:" << map->GetFloat(MouseX) << ", " << map->GetFloat(MouseY) << std::endl; + } +} +@end + +int main(int argc, char * argv[]) +{ + @autoreleasepool + { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} +#endif + diff --git a/samples/basic/basicsample_linux.cpp b/samples/basic/basicsample_linux.cpp new file mode 100644 index 00000000..0e4f8e1d --- /dev/null +++ b/samples/basic/basicsample_linux.cpp @@ -0,0 +1,119 @@ + +#include + +#if defined(GAINPUT_PLATFORM_LINUX) +#include +#include +#include +#include + + +// Define your user buttons +enum Button +{ + ButtonMenu, + ButtonConfirm, + MouseX, + MouseY +}; + + +const char* windowName = "Gainput basic sample"; +const int width = 800; +const int height = 600; + + +int main(int argc, char** argv) +{ + static int attributeListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER, /*In case single buffering is not supported*/ GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, + None }; + + Display* xDisplay = XOpenDisplay(0); + if (xDisplay == 0) + { + std::cerr << "Cannot connect to X server." << std::endl; + return -1; + } + + Window root = DefaultRootWindow(xDisplay); + + XVisualInfo* vi = glXChooseVisual(xDisplay, DefaultScreen(xDisplay), attributeListDbl); + assert(vi); + + GLXContext context = glXCreateContext(xDisplay, vi, 0, GL_TRUE); + + Colormap cmap = XCreateColormap(xDisplay, root, vi->visual, AllocNone); + + XSetWindowAttributes swa; + swa.colormap = cmap; + swa.event_mask = ExposureMask + | KeyPressMask | KeyReleaseMask + | PointerMotionMask | ButtonPressMask | ButtonReleaseMask; + + Window xWindow = XCreateWindow( + xDisplay, root, + 0, 0, width, height, 0, + CopyFromParent, InputOutput, + CopyFromParent, CWEventMask, + &swa + ); + + glXMakeCurrent(xDisplay, xWindow, context); + + XSetWindowAttributes xattr; + xattr.override_redirect = False; + XChangeWindowAttributes(xDisplay, xWindow, CWOverrideRedirect, &xattr); + + XMapWindow(xDisplay, xWindow); + XStoreName(xDisplay, xWindow, windowName); + + // Setup Gainput + gainput::InputManager manager; + const gainput::DeviceId mouseId = manager.CreateDevice(); + const gainput::DeviceId keyboardId = manager.CreateDevice(); + const gainput::DeviceId padId = manager.CreateDevice(); + + gainput::InputMap map(manager); + map.MapBool(ButtonMenu, keyboardId, gainput::KeyEscape); + map.MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + map.MapFloat(MouseX, mouseId, gainput::MouseAxisX); + map.MapFloat(MouseY, mouseId, gainput::MouseAxisY); + map.MapBool(ButtonConfirm, padId, gainput::PadButtonA); + + manager.SetDisplaySize(width, height); + + for (;;) + { + // Update Gainput + manager.Update(); + + XEvent event; + while (XPending(xDisplay)) + { + XNextEvent(xDisplay, &event); + manager.HandleEvent(event); + } + + // Check button states + if (map.GetBoolWasDown(ButtonMenu)) + { + std::cout << "Open menu!!" << std::endl; + } + if (map.GetBoolWasDown(ButtonConfirm)) + { + std::cout << "Confirmed!!" << std::endl; + } + + if (map.GetFloatDelta(MouseX) != 0.0f || map.GetFloatDelta(MouseY) != 0.0f) + { + std::cout << "Mouse: " << map.GetFloat(MouseX) << ", " << map.GetFloat(MouseY) << std::endl; + } + } + + XDestroyWindow(xDisplay, xWindow); + XCloseDisplay(xDisplay); + + return 0; +} +#endif + diff --git a/samples/basic/basicsample_mac.mm b/samples/basic/basicsample_mac.mm new file mode 100644 index 00000000..5abff3fe --- /dev/null +++ b/samples/basic/basicsample_mac.mm @@ -0,0 +1,106 @@ + +#include + +#if defined(GAINPUT_PLATFORM_MAC) +#include + +#import + +// Define your user buttons +enum Button +{ + ButtonMenu, + ButtonConfirm, + MouseX, + MouseY +}; + + +const char* windowName = "Gainput basic sample"; +const int width = 800; +const int height = 600; + +gainput::InputManager* manager; +gainput::InputMap* map; +gainput::DeviceId mouseId; +gainput::DeviceId keyboardId; +gainput::DeviceId padId; + +@interface Updater : NSObject +@end + +@implementation Updater +- (void)update:(NSTimer *)theTimer +{ + manager->Update(); + + // Check button states + if (map->GetBoolWasDown(ButtonConfirm)) + { + gainput::InputDevicePad* pad = static_cast(manager->GetDevice(padId)); + pad->Vibrate(1.0f, 0.0f); + } + if (map->GetBoolWasDown(ButtonMenu)) + { + gainput::InputDevicePad* pad = static_cast(manager->GetDevice(padId)); + pad->Vibrate(0.0f, 0.0f); + } + + if (map->GetBoolWasDown(ButtonMenu)) + { + std::cout << "Open Menu!!" << std::endl; + } + if (map->GetBoolWasDown(ButtonConfirm)) + { + std::cout << "Confirmed!!" << std::endl; + } + if (map->GetBool(ButtonConfirm)) + { + std::cout << "LM down" << std::endl; + } + + if (map->GetFloatDelta(MouseX) != 0.0f || map->GetFloatDelta(MouseY) != 0.0f) + { + std::cout << "Mouse:" << map->GetFloat(MouseX) << ", " << map->GetFloat(MouseY) << std::endl; + } +} +@end + +int main(int argc, char** argv) +{ + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + id window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, height) + styleMask:NSTitledWindowMask|NSClosableWindowMask backing:NSBackingStoreBuffered defer:NO]; + [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; + [window setTitle: @"Gainput Basic Sample"]; + [window makeKeyAndOrderFront:nil]; + + manager = new gainput::InputManager; + manager->SetDisplaySize(width, height); + mouseId = manager->CreateDevice(); + keyboardId = manager->CreateDevice(); + padId = manager->CreateDevice(); + + map = new gainput::InputMap(*manager); + map->MapBool(ButtonMenu, keyboardId, gainput::KeyEscape); + map->MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + map->MapFloat(MouseX, mouseId, gainput::MouseAxisX); + map->MapFloat(MouseY, mouseId, gainput::MouseAxisY); + map->MapBool(ButtonConfirm, padId, gainput::PadButtonA); + + Updater* updater = [[Updater alloc] init]; + + [NSTimer scheduledTimerWithTimeInterval:0.016 + target:updater + selector:@selector(update:) + userInfo:nil + repeats:YES]; + +// [NSApp activateIgnoringOtherApps:YES]; + [NSApp run]; + + return 0; +} +#endif + diff --git a/samples/basic/basicsample_win.cpp b/samples/basic/basicsample_win.cpp new file mode 100644 index 00000000..4f5befdb --- /dev/null +++ b/samples/basic/basicsample_win.cpp @@ -0,0 +1,159 @@ + +#include + +#if defined(GAINPUT_PLATFORM_WIN) + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +#include +#define LOG(...) {char buf[256]; sprintf(buf, __VA_ARGS__); OutputDebugStringA(buf); } + + +// Define your user buttons +enum Button +{ + ButtonMenu, + ButtonConfirm, + MouseX, + MouseY +}; + + +static char szWindowClass[] = "win32app"; +const char* windowName = "Gainput basic sample"; +const int width = 800; +const int height = 600; +bool doExit = false; + + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + HDC hdc; + char greeting[] = "Hello, World!"; + + switch (message) + { + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + TextOut(hdc, 5, 5, greeting, strlen(greeting)); + EndPaint(hWnd, &ps); + break; + case WM_DESTROY: + PostQuitMessage(0); + doExit = true; + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + break; + } + + return 0; +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = 0; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = 0; + + if (!RegisterClassEx(&wcex)) + { + MessageBox(NULL, "Call to RegisterClassEx failed!", "Gainput basic sample", NULL); + return 1; + } + + HWND hWnd = CreateWindow( + szWindowClass, + windowName, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + width, height, + NULL, + NULL, + hInstance, + NULL + ); + + if (!hWnd) + { + MessageBox(NULL, "Call to CreateWindow failed!", "Gainput basic sample", NULL); + return 1; + } + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + // Setup Gainput + gainput::InputManager manager; + manager.SetDisplaySize(width, height); + gainput::DeviceId mouseId = manager.CreateDevice(); + gainput::DeviceId keyboardId = manager.CreateDevice(); + gainput::DeviceId padId = manager.CreateDevice(); + + gainput::InputMap map(manager); + map.MapBool(ButtonMenu, keyboardId, gainput::KeyEscape); + map.MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + map.MapFloat(MouseX, mouseId, gainput::MouseAxisX); + map.MapFloat(MouseY, mouseId, gainput::MouseAxisY); + map.MapBool(ButtonConfirm, padId, gainput::PadButtonA); + + while (!doExit) + { + // Update Gainput + manager.Update(); + + MSG msg; + while (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + + // Forward any input messages to Gainput + manager.HandleMessage(msg); + } + + // Check button states + if (map.GetBoolWasDown(ButtonConfirm)) + { + gainput::InputDevicePad* pad = static_cast(manager.GetDevice(padId)); + pad->Vibrate(1.0f, 0.0f); + } + if (map.GetBoolWasDown(ButtonMenu)) + { + gainput::InputDevicePad* pad = static_cast(manager.GetDevice(padId)); + pad->Vibrate(0.0f, 0.0f); + } + + if (map.GetBoolWasDown(ButtonMenu)) + { + LOG("Open Menu!!\n"); + } + if (map.GetBoolWasDown(ButtonConfirm)) + { + LOG("Confirmed!!\n"); + } + + if (map.GetFloatDelta(MouseX) != 0.0f || map.GetFloatDelta(MouseY) != 0.0f) + { + LOG("Mouse: %f, %f\n", map.GetFloat(MouseX), map.GetFloat(MouseY)); + } + } + + return 0; +} +#endif + diff --git a/samples/dynamic/CMakeLists.txt b/samples/dynamic/CMakeLists.txt new file mode 100644 index 00000000..24c367ae --- /dev/null +++ b/samples/dynamic/CMakeLists.txt @@ -0,0 +1,25 @@ + +project(dynamicsample) + +include_directories(../../lib/include/) +include_directories(../samplefw/) + +file(GLOB_RECURSE sources *.cpp) +file(GLOB_RECURSE sfwsources ../samplefw/*.cpp) + +if(ANDROID) + add_library(dynamicsample SHARED ${sources} ${sfwsources}) +else() + add_executable(dynamicsample WIN32 ${sources} ${sfwsources}) +endif() + +target_link_libraries(dynamicsample gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(dynamicsample X11 GL rt) +elseif(WIN32) + target_link_libraries(dynamicsample ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(dynamicsample ${FOUNDATION} ${IOKIT} ${APPKIT}) +endif() + diff --git a/samples/dynamic/dynamicsample.cpp b/samples/dynamic/dynamicsample.cpp new file mode 100644 index 00000000..29032ced --- /dev/null +++ b/samples/dynamic/dynamicsample.cpp @@ -0,0 +1,216 @@ + +#include + +#include "../samplefw/SampleFramework.h" + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_WIN) +#define ENABLE_FILEIO +#include +#include +#include +#endif + + +enum Button +{ + ButtonReset, + ButtonLoad, + ButtonSave, + ButtonTest, + ButtonMouseX, + ButtonHttp, + ButtonCount +}; + + +void SaveInputMap(const gainput::InputMap& map) +{ + SFW_LOG("Saving: %s...\n", map.GetName()); + const gainput::InputManager& manager = map.GetManager(); +#ifdef ENABLE_FILEIO + std::ofstream of; + of.open("mappings.txt"); +#endif + const size_t maxCount = 32; + gainput::DeviceButtonSpec buttons[maxCount]; + for (int i = ButtonReset; i < ButtonCount; ++i) + { + if (!map.IsMapped(i)) + continue; + const size_t count = map.GetMappings(gainput::UserButtonId(i), buttons, maxCount); + for (size_t b = 0; b < count; ++b) + { + const gainput::InputDevice* device = manager.GetDevice(buttons[b].deviceId); + char name[maxCount]; + device->GetButtonName(buttons[b].buttonId, name, maxCount); + const gainput::ButtonType buttonType = device->GetButtonType(buttons[b].buttonId); + SFW_LOG("Btn %d: %d:%d (%s%d:%s) type=%d\n", i, buttons[b].deviceId, buttons[b].buttonId, device->GetTypeName(), device->GetIndex(), name, buttonType); +#ifdef ENABLE_FILEIO + of << i << " " << device->GetTypeName() << " " << device->GetIndex() << " " << name << " " << buttonType << std::endl; +#endif + } + } +#ifdef ENABLE_FILEIO + of.close(); +#endif +} + +void LoadInputMap(gainput::InputMap& map) +{ + SFW_LOG("Loading...\n"); +#ifdef ENABLE_FILEIO + const gainput::InputManager& manager = map.GetManager(); + std::ifstream ifs("mappings.txt"); + if(ifs.is_open()) + { + map.Clear(); + std::string line; + int i; + std::string typeName; + unsigned index; + std::string name; + int buttonType; + while (ifs.good()) + { + getline(ifs, line); + if (!ifs.good()) + break; + std::istringstream iss(line); + iss >> i; + iss >> typeName; + iss >> index; + iss >> name; + iss >> buttonType; + gainput::DeviceId deviceId = manager.FindDeviceId(typeName.c_str(), index); + const gainput::InputDevice* device = manager.GetDevice(deviceId); + gainput::DeviceButtonId button = device->GetButtonByName(name.c_str()); + SFW_LOG("Btn %d: %d:%d (%s%d:%s) type=%d\n", i, deviceId, button, typeName.c_str(), index, name.c_str(), buttonType); + if (buttonType == gainput::BT_BOOL) + { + map.MapBool(i, deviceId, button); + } + else if (buttonType == gainput::BT_FLOAT) + { + map.MapFloat(i, deviceId, button); + } + } + ifs.close(); + } +#endif +} + + +void SampleMain() +{ + SfwOpenWindow("Gainput: Dynamic sample"); + + gainput::InputManager manager; + + const gainput::DeviceId mouseId = manager.CreateDevice(); + manager.CreateDevice(); + const gainput::DeviceId keyboardId = manager.CreateDevice(); + manager.CreateDevice(); + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_WIN) + manager.SetDisplaySize(SfwGetWidth(), SfwGetHeight()); +#endif + + SfwSetInputManager(&manager); + + gainput::InputMap map(manager, "testmap"); + map.MapBool(ButtonReset, keyboardId, gainput::KeyEscape); + map.MapBool(ButtonSave, keyboardId, gainput::KeyF1); + map.MapBool(ButtonLoad, keyboardId, gainput::KeyF2); + map.MapBool(ButtonHttp, keyboardId, gainput::KeyF3); + map.MapFloat(ButtonMouseX, mouseId, gainput::MouseAxisX); + + gainput::DeviceButtonSpec anyButton[32]; + bool mapped = false; + + SFW_LOG("No button mapped, please press any button.\n"); + SFW_LOG("Press ESC to reset.\n"); + + bool doExit = false; + + while (!SfwIsDone() & !doExit) + { + manager.Update(); + +#if defined(GAINPUT_PLATFORM_LINUX) + XEvent event; + while (XPending(SfwGetXDisplay())) + { + XNextEvent(SfwGetXDisplay(), &event); + manager.HandleEvent(event); + if (event.type == DestroyNotify || event.type == ClientMessage) + { + doExit = true; + } + } +#elif defined(GAINPUT_PLATFORM_WIN) + MSG msg; + while (PeekMessage(&msg, SfwGetHWnd(), 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + manager.HandleMessage(msg); + } +#endif + + SfwUpdate(); + + if (map.GetBoolWasDown(ButtonReset)) + { + SFW_LOG("Mapping reset. Press any button.\n"); + mapped = false; + map.Unmap(ButtonTest); + } + + if (map.GetBoolWasDown(ButtonSave)) + { + SaveInputMap(map); + } + + if (map.GetBoolWasDown(ButtonLoad)) + { + LoadInputMap(map); + } + + if (map.GetBoolWasDown(ButtonHttp)) + { + static bool enabled = false; + enabled = !enabled; + gainput::DevSetHttp(enabled); + } + + + if (!mapped) + { + const size_t anyCount = manager.GetAnyButtonDown(anyButton, 32); + for (size_t i = 0; i < anyCount; ++i) + { + // Filter the returned buttons as needed. + const gainput::InputDevice* device = manager.GetDevice(anyButton[i].deviceId); + if (device->GetButtonType(anyButton[i].buttonId) == gainput::BT_BOOL + && map.GetUserButtonId(anyButton[i].deviceId, anyButton[i].buttonId) == gainput::InvalidDeviceButtonId) + { + SFW_LOG("Mapping to: %d:%d\n", anyButton[i].deviceId, anyButton[i].buttonId); + map.MapBool(ButtonTest, anyButton[i].deviceId, anyButton[i].buttonId); + mapped = true; + break; + } + } + } + else + { + if (map.GetBoolWasDown(ButtonTest)) + { + SFW_LOG("Button was down!\n"); + } + } + } + + SfwCloseWindow(); +} + + diff --git a/samples/gesture/CMakeLists.txt b/samples/gesture/CMakeLists.txt new file mode 100644 index 00000000..85c529b8 --- /dev/null +++ b/samples/gesture/CMakeLists.txt @@ -0,0 +1,25 @@ + +project(gesturesample) + +include_directories(../../lib/include/) +include_directories(../samplefw/) + +file(GLOB_RECURSE sources *.cpp) +file(GLOB_RECURSE sfwsources ../samplefw/*.cpp) + +if(ANDROID) + add_library(gesturesample SHARED ${sources} ${sfwsources}) +else() + add_executable(gesturesample WIN32 ${sources} ${sfwsources}) +endif() + +target_link_libraries(gesturesample gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(gesturesample X11 GL rt) +elseif(WIN32) + target_link_libraries(gesturesample ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(gesturesample ${FOUNDATION} ${IOKIT} ${APPKIT}) +endif() + diff --git a/samples/gesture/gesturesample.cpp b/samples/gesture/gesturesample.cpp new file mode 100644 index 00000000..ff67a0f9 --- /dev/null +++ b/samples/gesture/gesturesample.cpp @@ -0,0 +1,294 @@ + +#include + +#include "../samplefw/SampleFramework.h" + + +enum Button +{ + ButtonConfirm, + ButtonConfirmDouble, + ButtonConfirmExtra, + ButtonHoldGesture, + ButtonTapGesture, + ButtonPinching, + ButtonPinchScale, + ButtonRotating, + ButtonRotateAngle, +}; + + +const unsigned TouchPointCount = 8; +const unsigned TouchDataElems = 4; + +class MultiTouchEmulator : public gainput::InputDevice +{ +public: + MultiTouchEmulator(gainput::InputManager& manager, gainput::DeviceId device, unsigned index, gainput::InputDevice::DeviceVariant variant) : + gainput::InputDevice(manager, device, index == InputDevice::AutoIndex ? manager.GetDeviceCountByType(DT_CUSTOM) : 0) + { + state_ = manager.GetAllocator().New(manager.GetAllocator(), TouchPointCount*TouchDataElems); + GAINPUT_ASSERT(state_); + previousState_ = manager.GetAllocator().New(manager.GetAllocator(), TouchPointCount*TouchDataElems); + GAINPUT_ASSERT(previousState_); + } + + ~MultiTouchEmulator() + { + manager_.GetAllocator().Delete(state_); + manager_.GetAllocator().Delete(previousState_); + } + + void Initialize(gainput::DeviceId downDevice, gainput::DeviceButtonId downButton, + gainput::DeviceId xAxisDevice, gainput::DeviceButtonId xAxisButton, + gainput::DeviceId yAxisDevice, gainput::DeviceButtonId yAxisButton, + gainput::DeviceId downDevice2, gainput::DeviceButtonId downButton2, + gainput::DeviceId xAxisDevice2, gainput::DeviceButtonId xAxisButton2, + gainput::DeviceId yAxisDevice2, gainput::DeviceButtonId yAxisButton2) + { + isDown_ = false; + + downDevice_ = downDevice; + downButton_ = downButton; + xAxisDevice_ = xAxisDevice; + xAxisButton_ = xAxisButton; + yAxisDevice_ = yAxisDevice; + yAxisButton_ = yAxisButton; + downDevice2_ = downDevice2; + downButton2_ = downButton2; + xAxisDevice2_ = xAxisDevice2; + xAxisButton2_ = xAxisButton2; + yAxisDevice2_ = yAxisDevice2; + yAxisButton2_ = yAxisButton2; + } + + DeviceType GetType() const { return DT_CUSTOM; } + const char* GetTypeName() const { return "custom"; } + + bool IsValidButtonId(gainput::DeviceButtonId deviceButton) const + { + return deviceButton == gainput::Touch0Down + || deviceButton == gainput::Touch0X + || deviceButton == gainput::Touch0Y + || deviceButton == gainput::Touch1Down + || deviceButton == gainput::Touch1X + || deviceButton == gainput::Touch1Y; + } + + gainput::ButtonType GetButtonType(gainput::DeviceButtonId deviceButton) const + { + GAINPUT_ASSERT(IsValidButtonId(deviceButton)); + return (deviceButton == gainput::Touch0Down || deviceButton == gainput::Touch1Down) ? gainput::BT_BOOL : gainput::BT_FLOAT; + } + +protected: + void InternalUpdate(gainput::InputDeltaState* delta) + { + const gainput::InputDevice* downDevice = manager_.GetDevice(downDevice_); + GAINPUT_ASSERT(downDevice); + if (!downDevice->GetBool(downButton_) && downDevice->GetBoolPrevious(downButton_)) + { + isDown_ = !isDown_; + const gainput::InputDevice* xDevice = manager_.GetDevice(xAxisDevice_); + GAINPUT_ASSERT(xDevice); + x_ = xDevice->GetFloat(xAxisButton_); + const gainput::InputDevice* yDevice = manager_.GetDevice(yAxisDevice_); + GAINPUT_ASSERT(yDevice); + y_ = yDevice->GetFloat(yAxisButton_); + } + + state_->Set(gainput::Touch1Down, isDown_); + state_->Set(gainput::Touch1X, x_); + state_->Set(gainput::Touch1Y, y_); + + const gainput::InputDevice* downDevice2 = manager_.GetDevice(downDevice2_); + GAINPUT_ASSERT(downDevice2); + const gainput::InputDevice* xDevice2 = manager_.GetDevice(xAxisDevice2_); + GAINPUT_ASSERT(xDevice2); + const gainput::InputDevice* yDevice2 = manager_.GetDevice(yAxisDevice2_); + GAINPUT_ASSERT(yDevice2); + state_->Set(gainput::Touch0Down, downDevice2->GetBool(downButton2_)); + state_->Set(gainput::Touch0X, xDevice2->GetFloat(xAxisButton2_)); + state_->Set(gainput::Touch0Y, yDevice2->GetFloat(yAxisButton2_)); + } + + DeviceState InternalGetState() const { return DS_OK; } + +private: + bool isDown_; + float x_; + float y_; + gainput::DeviceId downDevice_; + gainput::DeviceButtonId downButton_; + gainput::DeviceId xAxisDevice_; + gainput::DeviceButtonId xAxisButton_; + gainput::DeviceId yAxisDevice_; + gainput::DeviceButtonId yAxisButton_; + gainput::DeviceId downDevice2_; + gainput::DeviceButtonId downButton2_; + gainput::DeviceId xAxisDevice2_; + gainput::DeviceButtonId xAxisButton2_; + gainput::DeviceId yAxisDevice2_; + gainput::DeviceButtonId yAxisButton2_; +}; + + +void SampleMain() +{ + SfwOpenWindow("Gainput: Gesture sample"); + + gainput::TrackingAllocator allocator(gainput::GetDefaultAllocator()); + + gainput::InputManager manager(true, allocator); + + const gainput::DeviceId keyboardId = manager.CreateDevice(); + const gainput::DeviceId mouseId = manager.CreateDevice(); + + gainput::InputDeviceTouch* touchDevice = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(touchDevice); + gainput::DeviceId touchId = touchDevice->GetDeviceId(); + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_WIN) + manager.SetDisplaySize(SfwGetWidth(), SfwGetHeight()); +#endif + + SfwSetInputManager(&manager); + + gainput::InputMap map(manager, "testmap", allocator); + + map.MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + + gainput::DoubleClickGesture* dcg = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(dcg); + dcg->Initialize(mouseId, gainput::MouseButtonLeft, + mouseId, gainput::MouseAxisX, 0.01f, + mouseId, gainput::MouseAxisY, 0.01f, + 500); + map.MapBool(ButtonConfirmDouble, dcg->GetDeviceId(), gainput::DoubleClickTriggered); + + gainput::SimultaneouslyDownGesture* sdg = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(sdg); + sdg->AddButton(mouseId, gainput::MouseButtonLeft); + sdg->AddButton(keyboardId, gainput::KeyShiftL); + map.MapBool(ButtonConfirmExtra, sdg->GetDeviceId(), gainput::SimultaneouslyDownTriggered); + + MultiTouchEmulator* mte = manager.CreateAndGetDevice(); + mte->Initialize(sdg->GetDeviceId(), gainput::SimultaneouslyDownTriggered, + mouseId, gainput::MouseAxisX, + mouseId, gainput::MouseAxisY, + mouseId, gainput::MouseButtonLeft, + mouseId, gainput::MouseAxisX, + mouseId, gainput::MouseAxisY); + + if (!touchDevice->IsAvailable() || touchDevice->GetVariant() == gainput::InputDevice::DV_NULL) + { + touchId = mte->GetDeviceId(); + } + + gainput::HoldGesture* hg = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(hg); + hg->Initialize(touchId, gainput::Touch0Down, + touchId, gainput::Touch0X, 0.1f, + touchId, gainput::Touch0Y, 0.1f, + true, + 800); + map.MapBool(ButtonHoldGesture, hg->GetDeviceId(), gainput::HoldTriggered); + + gainput::TapGesture* tg = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(tg); + tg->Initialize(touchId, gainput::Touch0Down, + 500); + map.MapBool(ButtonTapGesture, tg->GetDeviceId(), gainput::TapTriggered); + + gainput::PinchGesture* pg = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(pg); + pg->Initialize(touchId, gainput::Touch0Down, + touchId, gainput::Touch0X, + touchId, gainput::Touch0Y, + touchId, gainput::Touch1Down, + touchId, gainput::Touch1X, + touchId, gainput::Touch1Y); + map.MapBool(ButtonPinching, pg->GetDeviceId(), gainput::PinchTriggered); + map.MapFloat(ButtonPinchScale, pg->GetDeviceId(), gainput::PinchScale); + + gainput::RotateGesture* rg = manager.CreateAndGetDevice(); + GAINPUT_ASSERT(rg); + rg->Initialize(touchId, gainput::Touch0Down, + touchId, gainput::Touch0X, + touchId, gainput::Touch0Y, + touchId, gainput::Touch1Down, + touchId, gainput::Touch1X, + touchId, gainput::Touch1Y); + map.MapBool(ButtonRotating, rg->GetDeviceId(), gainput::RotateTriggered); + map.MapFloat(ButtonRotateAngle, rg->GetDeviceId(), gainput::RotateAngle); + + bool doExit = false; + + while (!SfwIsDone() && !doExit) + { + manager.Update(); + +#if defined(GAINPUT_PLATFORM_LINUX) + XEvent event; + while (XPending(SfwGetXDisplay())) + { + XNextEvent(SfwGetXDisplay(), &event); + manager.HandleEvent(event); + if (event.type == DestroyNotify || event.type == ClientMessage) + { + doExit = true; + } + } +#elif defined(GAINPUT_PLATFORM_WIN) + MSG msg; + while (PeekMessage(&msg, SfwGetHWnd(), 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + manager.HandleMessage(msg); + } +#endif + + SfwUpdate(); + + if (map.GetBoolWasDown(ButtonConfirm)) + { + SFW_LOG("Confirmed!\n"); + SFW_LOG("Memory: %u allocs, %u deallocs, %u used bytes\n", static_cast(allocator.GetAllocateCount()), static_cast(allocator.GetDeallocateCount()), static_cast(allocator.GetAllocatedMemory())); + } + + if (map.GetBoolWasDown(ButtonConfirmDouble)) + { + SFW_LOG("Confirmed doubly!\n"); + } + + if (map.GetBoolWasDown(ButtonConfirmExtra)) + { + SFW_LOG("Confirmed alternatively!\n"); + } + + if (map.GetBool(ButtonHoldGesture)) + { + SFW_LOG("Hold triggered!\n"); + } + + if (map.GetBoolWasDown(ButtonTapGesture)) + { + SFW_LOG("Tapped!\n"); + } + + if (map.GetBool(ButtonPinching)) + { + SFW_LOG("Pinching: %f\n", map.GetFloat(ButtonPinchScale)); + } + + if (map.GetBool(ButtonRotating)) + { + SFW_LOG("Rotation angle: %f\n", map.GetFloat(ButtonRotateAngle)); + } + } + + SfwCloseWindow(); +} + + diff --git a/samples/listener/CMakeLists.txt b/samples/listener/CMakeLists.txt new file mode 100644 index 00000000..0ef0b630 --- /dev/null +++ b/samples/listener/CMakeLists.txt @@ -0,0 +1,24 @@ + +project(listenersample) + +include_directories(../../lib/include/) +include_directories(../samplefw/) + +file(GLOB_RECURSE sources *.cpp) +file(GLOB_RECURSE sfwsources ../samplefw/*.cpp) + +if(ANDROID) + add_library(listenersample SHARED ${sources} ${sfwsources}) +else() + add_executable(listenersample WIN32 ${sources} ${sfwsources}) +endif() + +target_link_libraries(listenersample gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(listenersample X11 GL rt) +elseif(WIN32) + target_link_libraries(listenersample ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(listenersample ${FOUNDATION} ${IOKIT} ${APPKIT}) +endif() diff --git a/samples/listener/listenersample.cpp b/samples/listener/listenersample.cpp new file mode 100644 index 00000000..2cda7770 --- /dev/null +++ b/samples/listener/listenersample.cpp @@ -0,0 +1,175 @@ + +#include + +#include "../samplefw/SampleFramework.h" + + +enum Button +{ + ButtonToggleListener, + ButtonToggleMapListener, + ButtonConfirm, + ButtonMouseX, +}; + + +class MyDeviceButtonListener : public gainput::InputListener +{ +public: + MyDeviceButtonListener(gainput::InputManager& manager, int index) : manager_(manager), index_(index) { } + + bool OnDeviceButtonBool(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, bool oldValue, bool newValue) + { + const gainput::InputDevice* device = manager_.GetDevice(deviceId); + char buttonName[64] = ""; + device->GetButtonName(deviceButton, buttonName, 64); + SFW_LOG("(%d) Device %d (%s%d) bool button (%d/%s) changed: %d -> %d\n", index_, deviceId, device->GetTypeName(), device->GetIndex(), deviceButton, buttonName, oldValue, newValue); + return false; + } + + bool OnDeviceButtonFloat(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, float oldValue, float newValue) + { + const gainput::InputDevice* device = manager_.GetDevice(deviceId); + char buttonName[64] = ""; + device->GetButtonName(deviceButton, buttonName, 64); + SFW_LOG("(%d) Device %d (%s%d) float button (%d/%s) changed: %f -> %f\n", index_, deviceId, device->GetTypeName(), device->GetIndex(), deviceButton, buttonName, oldValue, newValue); + return true; + } + + int GetPriority() const + { + return index_; + } + +private: + gainput::InputManager& manager_; + int index_; +}; + +class MyUserButtonListener : public gainput::MappedInputListener +{ +public: + MyUserButtonListener(int index) : index_(index) { } + + bool OnUserButtonBool(gainput::UserButtonId userButton, bool oldValue, bool newValue) + { + SFW_LOG("(%d) User bool button %d changed: %d -> %d\n", index_, userButton, oldValue, newValue); + return true; + } + + bool OnUserButtonFloat(gainput::UserButtonId userButton, float oldValue, float newValue) + { + SFW_LOG("(%d) User float button %d changed: %f -> %f\n", index_, userButton, oldValue, newValue); + return true; + } + + int GetPriority() const + { + return index_; + } + +private: + int index_; +}; + + +void SampleMain() +{ + SfwOpenWindow("Gainput: Listener sample"); + + gainput::InputManager manager; + + manager.CreateDevice(gainput::InputDevice::DV_RAW); + const gainput::DeviceId keyboardId = manager.CreateDevice(); + manager.CreateDevice(gainput::InputDevice::DV_RAW); + const gainput::DeviceId mouseId = manager.CreateDevice(); + manager.CreateDevice(); + manager.CreateDevice(); + manager.CreateDevice(); + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_WIN) + manager.SetDisplaySize(SfwGetWidth(), SfwGetHeight()); +#endif + + SfwSetInputManager(&manager); + + gainput::InputMap map(manager, "testmap"); + + map.MapBool(ButtonToggleListener, keyboardId, gainput::KeyF1); + map.MapBool(ButtonToggleMapListener, keyboardId, gainput::KeyF2); + map.MapBool(ButtonConfirm, mouseId, gainput::MouseButtonLeft); + map.MapFloat(ButtonMouseX, mouseId, gainput::MouseAxisX); + + MyDeviceButtonListener myDeviceButtonListener(manager, 1); + gainput::ListenerId myDeviceButtonListenerId = manager.AddListener(&myDeviceButtonListener); + MyDeviceButtonListener myDeviceButtonListener2(manager, 2); + manager.AddListener(&myDeviceButtonListener2); + + MyUserButtonListener myUserButtonListener(2); + gainput::ListenerId myUserButtonListenerId = map.AddListener(&myUserButtonListener); + MyUserButtonListener myUserButtonListener2(1); + map.AddListener(&myUserButtonListener2); + + bool doExit = false; + + while (!SfwIsDone() && !doExit) + { + manager.Update(); + +#if defined(GAINPUT_PLATFORM_LINUX) + XEvent event; + while (XPending(SfwGetXDisplay())) + { + XNextEvent(SfwGetXDisplay(), &event); + manager.HandleEvent(event); + if (event.type == DestroyNotify || event.type == ClientMessage) + { + doExit = true; + } + } +#elif defined(GAINPUT_PLATFORM_WIN) + MSG msg; + while (PeekMessage(&msg, SfwGetHWnd(), 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + manager.HandleMessage(msg); + } +#endif + + SfwUpdate(); + + if (map.GetBoolWasDown(ButtonToggleListener)) + { + if (myDeviceButtonListenerId != gainput::ListenerId(-1)) + { + manager.RemoveListener(myDeviceButtonListenerId); + myDeviceButtonListenerId = gainput::ListenerId(-1); + SFW_LOG("Device button listener disabled.\n"); + } + else + { + myDeviceButtonListenerId = manager.AddListener(&myDeviceButtonListener); + SFW_LOG("Device button listener enabled.\n"); + } + } + + if (map.GetBoolWasDown(ButtonToggleMapListener)) + { + if (myUserButtonListenerId != gainput::ListenerId(-1)) + { + map.RemoveListener(myUserButtonListenerId); + myUserButtonListenerId = gainput::ListenerId(-1); + SFW_LOG("User button listener disabled.\n"); + } + else + { + myUserButtonListenerId = map.AddListener(&myUserButtonListener); + SFW_LOG("User button listener enabled.\n"); + } + } + } + + SfwCloseWindow(); +} + diff --git a/samples/recording/CMakeLists.txt b/samples/recording/CMakeLists.txt new file mode 100644 index 00000000..8742c1f8 --- /dev/null +++ b/samples/recording/CMakeLists.txt @@ -0,0 +1,24 @@ + +project(recordingsample) + +include_directories(../../lib/include/) +include_directories(../samplefw/) + +file(GLOB_RECURSE sources *.cpp) +file(GLOB_RECURSE sfwsources ../samplefw/*.cpp) + +if(ANDROID) + add_library(recordingsample SHARED ${sources} ${sfwsources}) +else() + add_executable(recordingsample WIN32 ${sources} ${sfwsources}) +endif() + +target_link_libraries(recordingsample gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(recordingsample X11 GL rt) +elseif(WIN32) + target_link_libraries(recordingsample ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(recordingsample ${FOUNDATION} ${IOKIT} ${APPKIT}) +endif() diff --git a/samples/recording/recordingsample.cpp b/samples/recording/recordingsample.cpp new file mode 100644 index 00000000..abe151e3 --- /dev/null +++ b/samples/recording/recordingsample.cpp @@ -0,0 +1,161 @@ + +#include + +#include "../samplefw/SampleFramework.h" + + +enum Button +{ + ButtonStartRecording, + ButtonStopRecording, + ButtonStartPlaying, + ButtonStopPlaying, + ButtonSerialize, +}; + + +class MyDeviceButtonListener : public gainput::InputListener +{ +public: + MyDeviceButtonListener(gainput::InputManager& manager) : manager(manager) { } + + bool OnDeviceButtonBool(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, bool oldValue, bool newValue) + { + const gainput::InputDevice* device = manager.GetDevice(deviceId); + char buttonName[64]; + device->GetButtonName(deviceButton, buttonName, 64); + SFW_LOG("Device %d (%s%d) bool button (%d/%s) changed: %d -> %d\n", deviceId, device->GetTypeName(), device->GetIndex(), deviceButton, buttonName, oldValue, newValue); + return true; + } + + bool OnDeviceButtonFloat(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, float oldValue, float newValue) + { + const gainput::InputDevice* device = manager.GetDevice(deviceId); + char buttonName[64]; + device->GetButtonName(deviceButton, buttonName, 64); + SFW_LOG("Device %d (%s%d) float button (%d/%s) changed: %f -> %f\n", deviceId, device->GetTypeName(), device->GetIndex(), deviceButton, buttonName, oldValue, newValue); + return true; + } + +private: + gainput::InputManager& manager; +}; + + +void SampleMain() +{ + SfwOpenWindow("Gainput: Listener sample"); + + gainput::InputManager manager; + + const gainput::DeviceId keyboardId = manager.CreateDevice(); + const gainput::DeviceId mouseId = manager.CreateDevice(); + manager.CreateDevice(); + manager.CreateDevice(); + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_WIN) + manager.SetDisplaySize(SfwGetWidth(), SfwGetHeight()); +#endif + + SfwSetInputManager(&manager); + + gainput::InputMap map(manager, "testmap"); + + map.MapBool(ButtonStartRecording, keyboardId, gainput::KeyF1); + map.MapBool(ButtonStopRecording, keyboardId, gainput::KeyF2); + map.MapBool(ButtonStartPlaying, keyboardId, gainput::KeyF3); + map.MapBool(ButtonStopPlaying, keyboardId, gainput::KeyF4); + map.MapBool(ButtonSerialize, keyboardId, gainput::KeyF5); + + MyDeviceButtonListener myDeviceButtonListener(manager); + manager.AddListener(&myDeviceButtonListener); + + gainput::InputRecorder inputRecorder(manager); + inputRecorder.AddDeviceToRecord(mouseId); + gainput::InputPlayer inputPlayer(manager); + + gainput::InputRecording* serializedRecording = 0; + + bool doExit = false; + + while (!SfwIsDone() && !doExit) + { + manager.Update(); + +#if defined(GAINPUT_PLATFORM_LINUX) + XEvent event; + while (XPending(SfwGetXDisplay())) + { + XNextEvent(SfwGetXDisplay(), &event); + manager.HandleEvent(event); + if (event.type == DestroyNotify || event.type == ClientMessage) + { + doExit = true; + } + } +#elif defined(GAINPUT_PLATFORM_WIN) + MSG msg; + while (PeekMessage(&msg, SfwGetHWnd(), 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + manager.HandleMessage(msg); + } +#endif + + SfwUpdate(); + + if (map.GetBoolWasDown(ButtonStartRecording)) + { + SFW_LOG("Started recording\n"); + inputRecorder.Start(); + } + else if (map.GetBoolWasDown(ButtonStopRecording)) + { + SFW_LOG("Stopped recording\n"); + inputRecorder.Stop(); + } + else if (map.GetBoolWasDown(ButtonStartPlaying)) + { + SFW_LOG("Started playing\n"); + inputPlayer.SetRecording(inputRecorder.GetRecording()); + inputPlayer.Start(); + } + else if (map.GetBoolWasDown(ButtonStopPlaying)) + { + SFW_LOG("Stopped playing\n"); + inputPlayer.Stop(); + } + else if (map.GetBoolWasDown(ButtonSerialize)) + { + inputPlayer.Stop(); + + SFW_LOG("Serializing recording\n"); + const size_t dataSize = inputRecorder.GetRecording()->GetSerializedSize(); + SFW_LOG("Size: %lu\n", dataSize); + void* serializedRecordingData = malloc(dataSize); + inputRecorder.GetRecording()->GetSerialized(manager, serializedRecordingData); + + SFW_LOG("Deserialzing recording\n"); + if (serializedRecording) + { + delete serializedRecording; + } + serializedRecording = new gainput::InputRecording(manager, serializedRecordingData, dataSize); + + SFW_LOG("Playing deserialized recording\n"); + inputPlayer.SetRecording(serializedRecording); + inputPlayer.Start(); + + free(serializedRecordingData); + } + } + + if (serializedRecording) + { + delete serializedRecording; + } + + SfwCloseWindow(); +} + diff --git a/samples/samplefw/SampleFramework.cpp b/samples/samplefw/SampleFramework.cpp new file mode 100644 index 00000000..7b55881b --- /dev/null +++ b/samples/samplefw/SampleFramework.cpp @@ -0,0 +1,359 @@ + +#include +#include "SampleFramework.h" + +extern void SampleMain(); + +const int width = 800; +const int height = 600; + + +int SfwGetWidth() +{ + return width; +} + +int SfwGetHeight() +{ + return height; +} + + +#if defined(GAINPUT_PLATFORM_LINUX) + +#include +#include +#include + +Display* xDisplay = 0; +Window xWindow; +GLXContext glxContext; + + +void SfwOpenWindow(const char* title) +{ + static int attributeListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER, /*In case single buffering is not supported*/ GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, + None }; + + xDisplay = XOpenDisplay(0); + if (xDisplay == 0) + { + SFW_LOG("Cannot connect to X server.\n"); + return; + } + + Window root = DefaultRootWindow(xDisplay); + + XVisualInfo* vi = glXChooseVisual(xDisplay, DefaultScreen(xDisplay), attributeListDbl); + assert(vi); + + glxContext = glXCreateContext(xDisplay, vi, 0, GL_TRUE); + + Colormap cmap = XCreateColormap(xDisplay, root, vi->visual, AllocNone); + + XSetWindowAttributes swa; + swa.colormap = cmap; + swa.event_mask = ExposureMask + | KeyPressMask | KeyReleaseMask + | PointerMotionMask | ButtonPressMask | ButtonReleaseMask; + + xWindow = XCreateWindow( + xDisplay, root, + 0, 0, width, height, 0, + CopyFromParent, InputOutput, + CopyFromParent, CWEventMask, + &swa + ); + + glXMakeCurrent(xDisplay, xWindow, glxContext); + + XSetWindowAttributes xattr; + xattr.override_redirect = False; + XChangeWindowAttributes(xDisplay, xWindow, CWOverrideRedirect, &xattr); + + XMapWindow(xDisplay, xWindow); + XStoreName(xDisplay, xWindow, title); + + Atom wmDelete=XInternAtom(xDisplay, "WM_DELETE_WINDOW", True); + XSetWMProtocols(xDisplay, xWindow, &wmDelete, 1); + + XFree(vi); +} + +void SfwCloseWindow() +{ + glXDestroyContext(xDisplay, glxContext); + XDestroyWindow(xDisplay, xWindow); + XCloseDisplay(xDisplay); +} + +void SfwUpdate() +{ +} + +bool SfwIsDone() +{ + return false; +} + +void SfwSetInputManager(gainput::InputManager* manager) +{ +} + +Display* SfwGetXDisplay() +{ + return xDisplay; +} + +int main(int argc, char** argv) +{ + SampleMain(); + return 0; +} + + +#elif defined(GAINPUT_PLATFORM_WIN) + +static char szWindowClass[] = "win32app"; +bool doExit = false; +HINSTANCE hInstance; +int nCmdShow; +HWND hWnd; + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + PAINTSTRUCT ps; + HDC hdc; + char greeting[] = "Hello, Gainput!"; + + switch (message) + { + case WM_PAINT: + hdc = BeginPaint(hWnd, &ps); + TextOut(hdc, 5, 5, greeting, strlen(greeting)); + EndPaint(hWnd, &ps); + break; + case WM_DESTROY: + PostQuitMessage(0); + doExit = true; + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + break; + } + + return 0; +} + +void SfwOpenWindow(const char* title) +{ + WNDCLASSEX wcex; + + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = 0; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = 0; + + if (!RegisterClassEx(&wcex)) + { + MessageBox(NULL, "Call to RegisterClassEx failed!", title, NULL); + return; + } + + hWnd = CreateWindow( + szWindowClass, + title, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, + width, height, + NULL, + NULL, + hInstance, + NULL + ); + + if (!hWnd) + { + MessageBox(NULL, "Call to CreateWindow failed!", title, NULL); + return; + } + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); +} + +void SfwCloseWindow() +{ +} + +void SfwUpdate() +{ +} + +bool SfwIsDone() +{ + return doExit; +} + +void SfwSetInputManager(gainput::InputManager* manager) +{ +} + +HWND SfwGetHWnd() +{ + return hWnd; +} + +int WINAPI WinMain(HINSTANCE hInstanceMain, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShowMain) +{ + hInstance = hInstanceMain; + nCmdShow = nCmdShowMain; + SampleMain(); + return 0; +} + + +#elif defined(GAINPUT_PLATFORM_ANDROID) + + +#include +#include + +#include +#include + +#include +#include +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "gainput", __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "gainput", __VA_ARGS__)) + +static bool doExit = false; +struct android_app* state; + +static int32_t MyHandleInput(struct android_app* app, AInputEvent* event) +{ + // Forward input events to Gainput + gainput::InputManager* inputManager = (gainput::InputManager*)app->userData; + static bool resSet = false; + if (!resSet) + { + inputManager->SetDisplaySize(ANativeWindow_getWidth(app->window), ANativeWindow_getHeight(app->window)); + resSet = true; + } + return inputManager->HandleInput(event); +} + +static void MyHandleCmd(struct android_app* app, int32_t cmd) +{ + switch (cmd) + { + case APP_CMD_SAVE_STATE: + break; + case APP_CMD_INIT_WINDOW: + break; + case APP_CMD_TERM_WINDOW: + doExit = true; + break; + case APP_CMD_LOST_FOCUS: + doExit = true; + break; + case APP_CMD_GAINED_FOCUS: + // bring back a certain functionality, like monitoring the accelerometer + break; + } +} + +void SfwOpenWindow(const char* title) +{ + app_dummy(); + LOGI("Opening window: %s\n", title); +} + +void SfwCloseWindow() +{ + ANativeActivity_finish(state->activity); + LOGI("Closing window\n"); +} + +void SfwUpdate() +{ + int ident; + int events; + struct android_poll_source* source; + + while (!doExit && (ident=ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0) + { + if (source != NULL) + { + source->process(state, source); + } + + if (state->destroyRequested != 0) + { + doExit = true; + } + } +} + +bool SfwIsDone() +{ + return doExit; +} + +void SfwSetInputManager(gainput::InputManager* manager) +{ + state->userData = manager; + state->onInputEvent = &MyHandleInput; + state->onAppCmd = &MyHandleCmd; +} + +void android_main(struct android_app* stateMain) +{ + state = stateMain; + + SampleMain(); +} + +#elif defined(GAINPUT_PLATFORM_MAC) + +void SfwOpenWindow(const char* title) +{ +} + +void SfwCloseWindow() +{ +} + +void SfwUpdate() +{ +} + +bool SfwIsDone() +{ + return false; +} + +void SfwSetInputManager(gainput::InputManager* manager) +{ +} + +int main(int argc, char** argv) +{ + SampleMain(); + return 0; +} + + + +#endif + diff --git a/samples/samplefw/SampleFramework.h b/samples/samplefw/SampleFramework.h new file mode 100644 index 00000000..408510df --- /dev/null +++ b/samples/samplefw/SampleFramework.h @@ -0,0 +1,48 @@ + +#ifndef SAMPLEFRAMEWORK_H_ +#define SAMPLEFRAMEWORK_H_ + + +void SfwOpenWindow(const char* title); +void SfwCloseWindow(); +void SfwUpdate(); +bool SfwIsDone(); + +void SfwSetInputManager(gainput::InputManager* manager); + +int SfwGetWidth(); +int SfwGetHeight(); + + +#if defined(GAINPUT_PLATFORM_LINUX) +#include + +Display* SfwGetXDisplay(); +#include +#define SFW_LOG(...) printf(__VA_ARGS__); + +#elif defined(GAINPUT_PLATFORM_WIN) + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +HWND SfwGetHWnd(); + +#include +#define SFW_LOG(...) { char buf[1024]; sprintf(buf, __VA_ARGS__); OutputDebugStringA(buf); } + +#elif defined(GAINPUT_PLATFORM_ANDROID) + +#include +#define SFW_LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "gainput", __VA_ARGS__)) + +#elif defined(GAINPUT_PLATFORM_MAC) + +#include +#define SFW_LOG(...) printf(__VA_ARGS__); + +#endif + +#endif + diff --git a/samples/sync/CMakeLists.txt b/samples/sync/CMakeLists.txt new file mode 100644 index 00000000..35bc1a0a --- /dev/null +++ b/samples/sync/CMakeLists.txt @@ -0,0 +1,24 @@ + +project(syncsample) + +include_directories(../../lib/include/) +include_directories(../samplefw/) + +file(GLOB_RECURSE sources *.cpp) +file(GLOB_RECURSE sfwsources ../samplefw/*.cpp) + +if(ANDROID) + add_library(syncsample SHARED ${sources} ${sfwsources}) +else() + add_executable(syncsample WIN32 ${sources} ${sfwsources}) +endif() + +target_link_libraries(syncsample gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(syncsample X11 GL rt) +elseif(WIN32) + target_link_libraries(syncsample ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(syncsample ${FOUNDATION} ${IOKIT} ${APPKIT}) +endif() diff --git a/samples/sync/syncsample.cpp b/samples/sync/syncsample.cpp new file mode 100644 index 00000000..bad64cc2 --- /dev/null +++ b/samples/sync/syncsample.cpp @@ -0,0 +1,120 @@ + +#include + +#include "../samplefw/SampleFramework.h" + + +enum Button +{ + ButtonClient, + ButtonTest, + ButtonMouseX, + ButtonMouseLeft, + ButtonCount +}; + +class MyDeviceButtonListener : public gainput::InputListener +{ +public: + MyDeviceButtonListener(gainput::InputManager& manager) : manager(manager) { } + + bool OnDeviceButtonBool(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, bool oldValue, bool newValue) + { + const gainput::InputDevice* device = manager.GetDevice(deviceId); + char buttonName[64]; + device->GetButtonName(deviceButton, buttonName, 64); + SFW_LOG("Device %d (%s%d) bool button (%d/%s) changed: %d -> %d\n", deviceId, device->GetTypeName(), device->GetIndex(), deviceButton, buttonName, oldValue, newValue); + return true; + } + + bool OnDeviceButtonFloat(gainput::DeviceId deviceId, gainput::DeviceButtonId deviceButton, float oldValue, float newValue) + { + const gainput::InputDevice* device = manager.GetDevice(deviceId); + char buttonName[64]; + device->GetButtonName(deviceButton, buttonName, 64); + SFW_LOG("Device %d (%s%d) float button (%d/%s) changed: %f -> %f\n", deviceId, device->GetTypeName(), device->GetIndex(), deviceButton, buttonName, oldValue, newValue); + return true; + } + +private: + gainput::InputManager& manager; +}; + +void SampleMain() +{ + SfwOpenWindow("Gainput: Sync sample"); + + gainput::InputManager manager; + + const gainput::DeviceId mouseId = manager.CreateDevice(); + manager.CreateDevice(); + const gainput::DeviceId keyboardId = manager.CreateDevice(); + manager.CreateDevice(); + +#if defined(GAINPUT_PLATFORM_LINUX) || defined(GAINPUT_PLATFORM_WIN) + manager.SetDisplaySize(SfwGetWidth(), SfwGetHeight()); +#endif + + SfwSetInputManager(&manager); + + gainput::InputMap map(manager, "testmap"); + map.MapBool(ButtonClient, keyboardId, gainput::KeyF1); + map.MapFloat(ButtonMouseX, mouseId, gainput::MouseAxisX); + map.MapFloat(ButtonMouseLeft, mouseId, gainput::MouseButtonLeft); + + MyDeviceButtonListener myDeviceButtonListener(manager); + manager.AddListener(&myDeviceButtonListener); + + SFW_LOG("Press F1 to connect to localhost and start syncing.\n"); + + bool doExit = false; + + while (!SfwIsDone() & !doExit) + { + manager.Update(); + +#if defined(GAINPUT_PLATFORM_LINUX) + XEvent event; + while (XPending(SfwGetXDisplay())) + { + XNextEvent(SfwGetXDisplay(), &event); + manager.HandleEvent(event); + if (event.type == DestroyNotify || event.type == ClientMessage) + { + doExit = true; + } + } +#elif defined(GAINPUT_PLATFORM_WIN) + MSG msg; + while (PeekMessage(&msg, SfwGetHWnd(), 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + manager.HandleMessage(msg); + } +#endif + + SfwUpdate(); + + if (map.GetBoolWasDown(ButtonClient)) + { + manager.ConnectForStateSync("127.0.0.1", 1211); + manager.StartDeviceStateSync(mouseId); + } + + if (map.GetBoolWasDown(ButtonMouseLeft)) + { + SFW_LOG("Mouse was down.\n"); + } + + if (map.GetFloatDelta(ButtonMouseX) != 0.0f) + { + SFW_LOG("Mouse X: %f\n", map.GetFloat(ButtonMouseX)); + } + + } + + SfwCloseWindow(); +} + + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..72b3b8cd --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,37 @@ + +project(gainputtest) + +include_directories(../lib/include/) +include_directories(../extern/catch/) + +file(GLOB_RECURSE sources *.cpp) + +if(CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +endif() + +if(ANDROID) + add_library(gainputtest SHARED ${sources}) +else() + add_executable(gainputtest WIN32 ${sources}) +endif() + +if(APPLE AND NOT IOS) + find_library(APPKIT AppKit) +else() + set(APPKIT "") +endif() + +target_link_libraries(gainputtest gainputstatic) + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(gainputtest X11 GL rt) +elseif(WIN32) + target_link_libraries(gainputtest ${XINPUT} ws2_32) +elseif(APPLE) + target_link_libraries(gainputtest ${FOUNDATION} ${IOKIT} ${APPKIT}) +endif() + +if(MSVC) + set_target_properties(gainputtest PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE") +endif(MSVC) diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 00000000..b390bca2 --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,17 @@ + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#define CATCH_CONFIG_RUNNER +#include "catch.hpp" +void android_main(struct android_app* stateMain) +{ + app_dummy(); + char* cmd[] = {"gainputtest"}; + Catch::Session().run( 1, cmd ); +} +#else +#define CATCH_CONFIG_MAIN +#include "catch.hpp" +#endif + + diff --git a/test/test_inputdevice.cpp b/test/test_inputdevice.cpp new file mode 100644 index 00000000..8215bef3 --- /dev/null +++ b/test/test_inputdevice.cpp @@ -0,0 +1,184 @@ + +#include "catch.hpp" + +#include + +using namespace gainput; + +TEST_CASE("InputDevice/keyboard", "") +{ + InputManager manager; + + InputDeviceKeyboard* device = manager.CreateAndGetDevice(); + + REQUIRE(device); + REQUIRE(device->GetIndex() == 0); + REQUIRE(device->GetDeviceId() != InvalidDeviceId); + REQUIRE(device->GetType() == InputDevice::DT_KEYBOARD); + REQUIRE(device->GetTypeName()); + + REQUIRE(device->IsValidButtonId(KeyEscape)); + REQUIRE(device->IsValidButtonId(KeyReturn)); + REQUIRE(device->IsValidButtonId(Key1)); + REQUIRE(device->IsValidButtonId(KeyA)); + REQUIRE(device->IsValidButtonId(KeyZ)); + REQUIRE(device->IsValidButtonId(KeySpace)); + + char buf[32]; + REQUIRE(device->GetButtonName(KeyF10, buf, 32) > 0); + REQUIRE(device->GetButtonName(KeyRightParenthesis, buf, 32) > 0); + REQUIRE(device->GetButtonName(KeyPeriod, buf, 32) > 0); + REQUIRE(device->GetButtonName(KeyV, buf, 32) > 0); + REQUIRE(device->GetButtonName(KeySpace, buf, 32) > 0); + + REQUIRE(device->GetButtonType(KeyEscape) == BT_BOOL); + REQUIRE(device->GetButtonType(Key2) == BT_BOOL); + REQUIRE(device->GetButtonType(KeyD) == BT_BOOL); + REQUIRE(device->GetButtonType(KeyBackSpace) == BT_BOOL); + + REQUIRE(device->GetButtonByName("space") == KeySpace); + REQUIRE(device->GetButtonByName("5") == Key5); + + REQUIRE(device->GetInputState()); + REQUIRE(device->GetPreviousInputState()); + + REQUIRE(device->IsTextInputEnabled()); + device->SetTextInputEnabled(false); + REQUIRE(!device->IsTextInputEnabled()); +} + +TEST_CASE("InputDevice/mouse", "") +{ + InputManager manager; + + InputDeviceMouse* device = manager.CreateAndGetDevice(); + + REQUIRE(device); + REQUIRE(device->GetIndex() == 0); + REQUIRE(device->GetDeviceId() != InvalidDeviceId); + REQUIRE(device->GetType() == InputDevice::DT_MOUSE); + REQUIRE(device->GetTypeName()); + + REQUIRE(device->IsValidButtonId(MouseButtonLeft)); + REQUIRE(device->IsValidButtonId(MouseButtonMiddle)); + REQUIRE(device->IsValidButtonId(MouseButtonRight)); + REQUIRE(device->IsValidButtonId(MouseAxisX)); + REQUIRE(device->IsValidButtonId(MouseAxisY)); + + char buf[32]; + REQUIRE(device->GetButtonName(MouseButtonLeft, buf, 32) > 0); + REQUIRE(device->GetButtonName(MouseButtonRight, buf, 32) > 0); + REQUIRE(device->GetButtonName(MouseButton14, buf, 32) > 0); + REQUIRE(device->GetButtonName(MouseAxisX, buf, 32) > 0); + REQUIRE(device->GetButtonName(MouseAxisY, buf, 32) > 0); + + REQUIRE(device->GetButtonType(MouseButtonLeft) == BT_BOOL); + REQUIRE(device->GetButtonType(MouseButtonMiddle) == BT_BOOL); + REQUIRE(device->GetButtonType(MouseButtonRight) == BT_BOOL); + REQUIRE(device->GetButtonType(MouseAxisX) == BT_FLOAT); + REQUIRE(device->GetButtonType(MouseAxisY) == BT_FLOAT); + + REQUIRE(device->GetButtonByName("mouse_left") == MouseButtonLeft); + REQUIRE(device->GetButtonByName("mouse_right") == MouseButtonRight); + REQUIRE(device->GetButtonByName("mouse_11") == MouseButton11); + REQUIRE(device->GetButtonByName("mouse_x") == MouseAxisX); + REQUIRE(device->GetButtonByName("mouse_y") == MouseAxisY); + + REQUIRE(device->GetInputState()); + REQUIRE(device->GetPreviousInputState()); +} + +TEST_CASE("InputDevice/pad", "") +{ + InputManager manager; + + InputDevicePad* device = manager.CreateAndGetDevice(); + + REQUIRE(device); + REQUIRE(device->GetIndex() == 0); + REQUIRE(device->GetDeviceId() != InvalidDeviceId); + REQUIRE(device->GetType() == InputDevice::DT_PAD); + REQUIRE(device->GetTypeName()); + + REQUIRE(device->IsValidButtonId(PadButtonLeftStickX)); + REQUIRE(device->IsValidButtonId(PadButtonLeftStickY)); + REQUIRE(device->IsValidButtonId(PadButtonLeft)); + REQUIRE(device->IsValidButtonId(PadButtonRight)); + REQUIRE(device->IsValidButtonId(PadButtonUp)); + REQUIRE(device->IsValidButtonId(PadButtonDown)); + REQUIRE(device->IsValidButtonId(PadButtonA)); + REQUIRE(device->IsValidButtonId(PadButtonB)); + REQUIRE(device->IsValidButtonId(PadButtonX)); + REQUIRE(device->IsValidButtonId(PadButtonY)); + REQUIRE(device->IsValidButtonId(PadButtonL1)); + REQUIRE(device->IsValidButtonId(PadButtonR1)); + + char buf[32]; + REQUIRE(device->GetButtonName(PadButtonLeftStickX, buf, 32) > 0); + REQUIRE(device->GetButtonName(PadButtonB, buf, 32) > 0); + REQUIRE(device->GetButtonName(PadButtonAxis19, buf, 32) > 0); + REQUIRE(device->GetButtonName(PadButtonL2, buf, 32) > 0); + REQUIRE(device->GetButtonName(PadButtonGyroscopeY, buf, 32) > 0); + + REQUIRE(device->GetButtonType(PadButtonLeft) == BT_BOOL); + REQUIRE(device->GetButtonType(PadButtonDown) == BT_BOOL); + REQUIRE(device->GetButtonType(PadButtonA) == BT_BOOL); + REQUIRE(device->GetButtonType(PadButtonB) == BT_BOOL); + REQUIRE(device->GetButtonType(PadButtonL1) == BT_BOOL); + REQUIRE(device->GetButtonType(PadButtonLeftStickX) == BT_FLOAT); + REQUIRE(device->GetButtonType(PadButtonLeftStickY) == BT_FLOAT); + REQUIRE(device->GetButtonType(PadButtonAxis15) == BT_FLOAT); + + REQUIRE(device->GetButtonByName("pad_left_stick_x") == PadButtonLeftStickX); + REQUIRE(device->GetButtonByName("pad_left_stick_y") == PadButtonLeftStickY); + REQUIRE(device->GetButtonByName("pad_acceleration_x") == PadButtonAccelerationX); + REQUIRE(device->GetButtonByName("pad_button_a") == PadButtonA); + REQUIRE(device->GetButtonByName("pad_button_y") == PadButtonY); + + REQUIRE(device->GetInputState()); + REQUIRE(device->GetPreviousInputState()); +} + +TEST_CASE("InputDevice/touch", "") +{ + InputManager manager; + + InputDeviceTouch* device = manager.CreateAndGetDevice(); + + REQUIRE(device); + REQUIRE(device->GetIndex() == 0); + REQUIRE(device->GetDeviceId() != InvalidDeviceId); + REQUIRE(device->GetType() == InputDevice::DT_TOUCH); + REQUIRE(device->GetTypeName()); + + REQUIRE(device->IsValidButtonId(Touch0Down)); + REQUIRE(device->IsValidButtonId(Touch0X)); + REQUIRE(device->IsValidButtonId(Touch0Y)); + REQUIRE(device->IsValidButtonId(Touch1Down)); + REQUIRE(device->IsValidButtonId(Touch1X)); + REQUIRE(device->IsValidButtonId(Touch1Y)); + + char buf[32]; + REQUIRE(device->GetButtonName(Touch0Down, buf, 32) > 0); + REQUIRE(device->GetButtonName(Touch0X, buf, 32) > 0); + REQUIRE(device->GetButtonName(Touch0Y, buf, 32) > 0); + REQUIRE(device->GetButtonName(Touch2Down, buf, 32) > 0); + + REQUIRE(device->GetButtonType(Touch0Down) == BT_BOOL); + REQUIRE(device->GetButtonType(Touch0X) == BT_FLOAT); + REQUIRE(device->GetButtonType(Touch0Y) == BT_FLOAT); + REQUIRE(device->GetButtonType(Touch3Down) == BT_BOOL); + REQUIRE(device->GetButtonType(Touch3X) == BT_FLOAT); + REQUIRE(device->GetButtonType(Touch3Y) == BT_FLOAT); + + REQUIRE(device->GetButtonByName("touch_0_down") == Touch0Down); + REQUIRE(device->GetButtonByName("touch_0_x") == Touch0X); + REQUIRE(device->GetButtonByName("touch_0_y") == Touch0Y); + REQUIRE(device->GetButtonByName("touch_6_down") == Touch6Down); + REQUIRE(device->GetButtonByName("touch_6_x") == Touch6X); + REQUIRE(device->GetButtonByName("touch_6_y") == Touch6Y); + + REQUIRE(device->GetInputState()); + REQUIRE(device->GetPreviousInputState()); +} + diff --git a/test/test_inputmanager.cpp b/test/test_inputmanager.cpp new file mode 100644 index 00000000..eb9ae82a --- /dev/null +++ b/test/test_inputmanager.cpp @@ -0,0 +1,79 @@ + +#include "catch.hpp" + +#include + +using namespace gainput; + +TEST_CASE("InputManager/CreateDevice", "") +{ + InputManager manager; + + const DeviceId deviceId = manager.CreateDevice(); + REQUIRE(deviceId != InvalidDeviceId); + + InputDevice* device = manager.GetDevice(deviceId); + REQUIRE(device); + REQUIRE(device->GetType() == InputDevice::DT_KEYBOARD); + REQUIRE(device->GetIndex() == 0); + + const DeviceId deviceId2 = manager.CreateDevice(); + REQUIRE(deviceId2 != InvalidDeviceId); + + InputDevice* device2 = manager.GetDevice(deviceId2); + REQUIRE(device2); + REQUIRE(device2->GetType() == InputDevice::DT_MOUSE); + REQUIRE(device2->GetIndex() == 0); + + InputDevicePad* device3 = manager.CreateAndGetDevice(); + REQUIRE(device3); + REQUIRE(device3->GetType() == InputDevice::DT_PAD); + REQUIRE(device3->GetIndex() == 0); + REQUIRE(device3->GetDeviceId() != InvalidDeviceId); +} + +TEST_CASE("InputManager/FindDeviceId", "") +{ + InputManager manager; + const DeviceId deviceId = manager.CreateDevice(); + const InputDevice* device = manager.GetDevice(deviceId); + const DeviceId deviceId2 = manager.CreateDevice(); + const InputDevice* device2 = manager.GetDevice(deviceId2); + const DeviceId deviceId3 = manager.CreateDevice(); + const InputDevice* device3 = manager.GetDevice(deviceId3); + + REQUIRE(manager.FindDeviceId(device->GetTypeName(), device->GetIndex()) == deviceId); + REQUIRE(manager.FindDeviceId(device->GetType(), device->GetIndex()) == deviceId); + + REQUIRE(manager.FindDeviceId(device2->GetTypeName(), device2->GetIndex()) == deviceId2); + REQUIRE(manager.FindDeviceId(device2->GetType(), device2->GetIndex()) == deviceId2); + + REQUIRE(manager.FindDeviceId(device3->GetTypeName(), device3->GetIndex()) == deviceId3); + REQUIRE(manager.FindDeviceId(device3->GetType(), device3->GetIndex()) == deviceId3); +} + +TEST_CASE("InputManager/GetDeviceCountByType", "") +{ + InputManager manager; + + for (int type = InputDevice::DT_MOUSE; type < InputDevice::DT_COUNT; ++type) + { + REQUIRE(manager.GetDeviceCountByType(InputDevice::DeviceType(type)) == 0); + } + + manager.CreateDevice(); + manager.CreateDevice(); + manager.CreateDevice(); + + REQUIRE(manager.GetDeviceCountByType(InputDevice::DT_KEYBOARD) == 1); + REQUIRE(manager.GetDeviceCountByType(InputDevice::DT_MOUSE) == 1); + REQUIRE(manager.GetDeviceCountByType(InputDevice::DT_PAD) == 1); + REQUIRE(manager.GetDeviceCountByType(InputDevice::DT_TOUCH) == 0); +} + +TEST_CASE("InputManager/GetAnyButtonDown", "") +{ + InputManager manager; + REQUIRE(manager.GetAnyButtonDown(0, 0) == 0); +} + diff --git a/test/test_inputmap.cpp b/test/test_inputmap.cpp new file mode 100644 index 00000000..2eda6038 --- /dev/null +++ b/test/test_inputmap.cpp @@ -0,0 +1,177 @@ + +#include "catch.hpp" + +#include + +using namespace gainput; + +enum TestButtons +{ + ButtonA, + ButtonStart = ButtonA, + ButtonB, + ButtonC, + ButtonD, + ButtonE, + ButtonF, + ButtonG, + ButtonCount +}; + +TEST_CASE("InputMap/create", "") +{ + InputManager manager; + InputMap map(manager, "testmap"); + + REQUIRE(&manager == &map.GetManager()); + REQUIRE(map.GetName()); + REQUIRE(strcmp(map.GetName(), "testmap") == 0); + + for (int b = ButtonStart; b < ButtonCount; ++b) + { + REQUIRE(!map.IsMapped(b)); + } + + InputMap map2(manager); + REQUIRE(!map2.GetName()); +} + +TEST_CASE("InputMap/map_bool", "") +{ + InputManager manager; + const DeviceId keyboardId = manager.CreateDevice(); + const DeviceId mouseId = manager.CreateDevice(); + InputMap map(manager, "testmap"); + + REQUIRE(map.MapBool(ButtonA, keyboardId, KeyA)); + REQUIRE(map.MapBool(ButtonA, keyboardId, KeyB)); + REQUIRE(map.MapBool(ButtonA, keyboardId, KeyEscape)); + REQUIRE(map.MapBool(ButtonA, mouseId, MouseButtonLeft)); + REQUIRE(map.IsMapped(ButtonA)); + + REQUIRE(map.MapBool(ButtonB, keyboardId, KeyF2)); + REQUIRE(map.MapBool(ButtonB, mouseId, MouseButtonLeft)); + REQUIRE(map.IsMapped(ButtonB)); + + map.Clear(); + for (int b = ButtonStart; b < ButtonCount; ++b) + { + REQUIRE(!map.IsMapped(b)); + } + + REQUIRE(map.MapBool(ButtonA, mouseId, MouseButtonRight)); + REQUIRE(map.IsMapped(ButtonA)); + + map.Unmap(ButtonA); + REQUIRE(!map.IsMapped(ButtonA)); + + DeviceButtonSpec mappings[32]; + REQUIRE(map.GetMappings(ButtonA, mappings, 32) == 0); + + REQUIRE(map.MapBool(ButtonA, mouseId, MouseButtonMiddle)); + REQUIRE(map.MapBool(ButtonA, keyboardId, KeyD)); + REQUIRE(map.MapBool(ButtonD, keyboardId, KeyB)); + + REQUIRE(map.GetMappings(ButtonA, mappings, 32) == 2); + REQUIRE(mappings[0].deviceId == mouseId); + REQUIRE(mappings[0].buttonId == MouseButtonMiddle); + REQUIRE(mappings[1].deviceId == keyboardId); + REQUIRE(mappings[1].buttonId == KeyD); + + char buf[32]; + REQUIRE(map.GetUserButtonName(ButtonA, buf, 32)); + + REQUIRE(map.GetUserButtonId(mouseId, MouseButtonMiddle) == ButtonA); + REQUIRE(map.GetUserButtonId(keyboardId, KeyD) == ButtonA); + REQUIRE(map.GetUserButtonId(keyboardId, KeyB) == ButtonD); +} + +TEST_CASE("InputMap/map_float", "") +{ + InputManager manager; + const DeviceId keyboardId = manager.CreateDevice(); + const DeviceId mouseId = manager.CreateDevice(); + const DeviceId padId = manager.CreateDevice(); + InputMap map(manager, "testmap"); + + REQUIRE(map.MapFloat(ButtonA, keyboardId, KeyA)); + REQUIRE(map.MapFloat(ButtonA, keyboardId, KeyB)); + REQUIRE(map.MapFloat(ButtonA, keyboardId, KeyEscape)); + REQUIRE(map.MapFloat(ButtonA, mouseId, MouseButtonLeft)); + REQUIRE(map.MapFloat(ButtonA, mouseId, MouseAxisY)); + REQUIRE(map.MapFloat(ButtonA, padId, PadButtonLeftStickX)); + REQUIRE(map.IsMapped(ButtonA)); + + REQUIRE(map.MapFloat(ButtonB, keyboardId, KeyF2)); + REQUIRE(map.MapFloat(ButtonB, mouseId, MouseAxisX)); + REQUIRE(map.IsMapped(ButtonB)); + + map.Clear(); + for (int b = ButtonStart; b < ButtonCount; ++b) + { + REQUIRE(!map.IsMapped(b)); + } + + REQUIRE(map.MapFloat(ButtonA, mouseId, MouseAxisX)); + REQUIRE(map.IsMapped(ButtonA)); + + map.Unmap(ButtonA); + REQUIRE(!map.IsMapped(ButtonA)); + + DeviceButtonSpec mappings[32]; + REQUIRE(map.GetMappings(ButtonA, mappings, 32) == 0); + + REQUIRE(map.MapFloat(ButtonA, mouseId, MouseAxisX)); + REQUIRE(map.MapFloat(ButtonA, keyboardId, KeyF5)); + REQUIRE(map.MapFloat(ButtonD, padId, PadButtonLeftStickY)); + + REQUIRE(map.GetMappings(ButtonA, mappings, 32) == 2); + REQUIRE(mappings[0].deviceId == mouseId); + REQUIRE(mappings[0].buttonId == MouseAxisX); + REQUIRE(mappings[1].deviceId == keyboardId); + REQUIRE(mappings[1].buttonId == KeyF5); + + char buf[32]; + REQUIRE(map.GetUserButtonName(ButtonA, buf, 32)); + + REQUIRE(map.GetUserButtonId(mouseId, MouseAxisX) == ButtonA); + REQUIRE(map.GetUserButtonId(keyboardId, KeyF5) == ButtonA); + REQUIRE(map.GetUserButtonId(padId, PadButtonLeftStickY) == ButtonD); +} + +TEST_CASE("InputMap/SetDeadZone_SetUserButtonPolicy", "") +{ + InputManager manager; + const DeviceId keyboardId = manager.CreateDevice(); + const DeviceId mouseId = manager.CreateDevice(); + const DeviceId padId = manager.CreateDevice(); + InputMap map(manager, "testmap"); + + for (int b = ButtonStart; b < ButtonCount; ++b) + { + REQUIRE(!map.SetDeadZone(b, 0.1f)); + REQUIRE(!map.SetUserButtonPolicy(b, InputMap::UBP_AVERAGE)); + } + + REQUIRE(map.MapFloat(ButtonA, mouseId, MouseAxisX)); + + REQUIRE(map.SetDeadZone(ButtonA, 0.01f)); + REQUIRE(map.SetDeadZone(ButtonA, 0.0f)); + REQUIRE(map.SetDeadZone(ButtonA, 1.01f)); + REQUIRE(!map.SetDeadZone(ButtonF, 1.01f)); + + REQUIRE(map.SetUserButtonPolicy(ButtonA, InputMap::UBP_AVERAGE)); + REQUIRE(map.SetUserButtonPolicy(ButtonA, InputMap::UBP_MAX)); + REQUIRE(map.SetUserButtonPolicy(ButtonA, InputMap::UBP_MIN)); + REQUIRE(map.SetUserButtonPolicy(ButtonA, InputMap::UBP_FIRST_DOWN)); + + map.Clear(); + + for (int b = ButtonStart; b < ButtonCount; ++b) + { + REQUIRE(!map.SetDeadZone(b, 0.1f)); + REQUIRE(!map.SetUserButtonPolicy(b, InputMap::UBP_AVERAGE)); + } +} + + diff --git a/test/test_inputrecording.cpp b/test/test_inputrecording.cpp new file mode 100644 index 00000000..1e687cb7 --- /dev/null +++ b/test/test_inputrecording.cpp @@ -0,0 +1,133 @@ + +#include "catch.hpp" + +#include + +using namespace gainput; + +TEST_CASE("InputRecording/general", "") +{ + InputRecording recording; + + REQUIRE(recording.GetDuration() == 0); + REQUIRE(recording.GetSerializedSize() > 0); + + recording.AddChange(100, 0, 0, true); + + REQUIRE(recording.GetDuration() == 100); + + RecordedDeviceButtonChange change; + + REQUIRE(!recording.GetNextChange(50, change)); + REQUIRE(recording.GetNextChange(100, change)); + REQUIRE(change.time == 100); + REQUIRE(change.deviceId == 0); + REQUIRE(change.buttonId == 0); + REQUIRE(change.b == true); + + REQUIRE(!recording.GetNextChange(1000, change)); + + recording.Reset(); + recording.AddChange(150, 5, 102, 0.8f); + + REQUIRE(!recording.GetNextChange(50, change)); + REQUIRE(recording.GetNextChange(101, change)); + REQUIRE(recording.GetNextChange(160, change)); + REQUIRE(change.time == 150); + REQUIRE(change.deviceId == 5); + REQUIRE(change.buttonId == 102); + REQUIRE(change.f == 0.8f); + REQUIRE(!recording.GetNextChange(1000, change)); + + recording.Reset(); + recording.Clear(); + + REQUIRE(recording.GetDuration() == 0); + REQUIRE(!recording.GetNextChange(10000, change)); +} + +TEST_CASE("InputRecording/serialization", "") +{ + InputManager manager; + const DeviceId deviceId = manager.CreateDevice(); + InputRecording recording; + + recording.AddChange(500, deviceId, MouseButtonLeft, true); + recording.AddChange(600, deviceId, MouseButtonMiddle, false); + recording.AddChange(700, deviceId, MouseAxisX, 0.2f); + + REQUIRE(recording.GetSerializedSize() > 0); + + void* serializedData = malloc(recording.GetSerializedSize()); + recording.GetSerialized(manager, serializedData); + + InputRecording recording2(manager, serializedData, recording.GetSerializedSize()); + + free(serializedData); + + REQUIRE(recording.GetSerializedSize() == recording2.GetSerializedSize()); + REQUIRE(recording.GetDuration() == recording2.GetDuration()); + + RecordedDeviceButtonChange change, change2; + + for (unsigned t = 500; t <= 700; t += 100) + { + REQUIRE(recording.GetNextChange(t, change)); + REQUIRE(recording2.GetNextChange(t, change2)); + REQUIRE(change.time == change2.time); + REQUIRE(change.deviceId == change2.deviceId); + REQUIRE(change.buttonId == change2.buttonId); + if (t == 700) + { + REQUIRE(change.f == change2.f); + } + else + { + REQUIRE(change.b == change2.b); + } + } +} + +TEST_CASE("InputPlayer", "") +{ + InputManager manager; + InputPlayer player(manager); + + REQUIRE(!player.GetRecording()); + + InputRecording recording;; + player.SetRecording(&recording); + + REQUIRE(player.GetRecording() == &recording); + + REQUIRE(!player.IsPlaying()); + player.Start(); + REQUIRE(player.IsPlaying()); + player.Stop(); + REQUIRE(!player.IsPlaying()); +} + +TEST_CASE("InputRecorder", "") +{ + InputManager manager; + + InputRecorder recorder(manager); + + REQUIRE(!recorder.IsRecording()); + recorder.Start(); + REQUIRE(recorder.IsRecording()); + recorder.Stop(); + REQUIRE(!recorder.IsRecording()); + + REQUIRE(recorder.GetRecording()); + + REQUIRE(recorder.IsDeviceToRecord(0)); + REQUIRE(recorder.IsDeviceToRecord(1)); + REQUIRE(recorder.IsDeviceToRecord(2358349)); + + recorder.AddDeviceToRecord(0); + REQUIRE(recorder.IsDeviceToRecord(0)); + REQUIRE(!recorder.IsDeviceToRecord(1)); + REQUIRE(!recorder.IsDeviceToRecord(2358349)); +} + diff --git a/test/test_inputstate.cpp b/test/test_inputstate.cpp new file mode 100644 index 00000000..a186f29b --- /dev/null +++ b/test/test_inputstate.cpp @@ -0,0 +1,48 @@ + +#include "catch.hpp" + +#include + +using namespace gainput; + +const unsigned ButtonCount = 15; + +TEST_CASE("InputState", "") +{ + InputState state(GetDefaultAllocator(), ButtonCount); + InputState state2(GetDefaultAllocator(), ButtonCount); + + for (unsigned i = 0; i < ButtonCount; ++i) + { + REQUIRE(!state.GetBool(i)); + REQUIRE(state.GetFloat(i) == 0.0f); + } + + for (unsigned i = 0; i < ButtonCount; ++i) + { + const bool s = i & 1; + state.Set(i, s); + REQUIRE(state.GetBool(i) == s); + } + + state2 = state; + for (unsigned i = 0; i < ButtonCount; ++i) + { + REQUIRE(state2.GetBool(i) == state.GetBool(i)); + } + + float s = 0.0f; + for (unsigned i = 0; i < ButtonCount; ++i) + { + state.Set(i, s); + REQUIRE(state.GetFloat(i) == s); + s += float(i)*0.0354f; + } + + state2 = state; + for (unsigned i = 0; i < ButtonCount; ++i) + { + REQUIRE(state2.GetFloat(i) == state.GetFloat(i)); + } +} + diff --git a/tools/html5client/index.html b/tools/html5client/index.html new file mode 100644 index 00000000..1024cdc2 --- /dev/null +++ b/tools/html5client/index.html @@ -0,0 +1,40 @@ + + +Gainput Javascript touch client + + + + +
+ +

+Host:
+Port:
+

+ +

+Touch Device: +

+ +

+ +

+ + + + diff --git a/tools/html5client/touch.html b/tools/html5client/touch.html new file mode 100644 index 00000000..3472fa03 --- /dev/null +++ b/tools/html5client/touch.html @@ -0,0 +1,159 @@ + + +Gainput Javascript touch client + + + + + + + + + +