From eee6dab226b2f056598d621aeed16691bbce7fe7 Mon Sep 17 00:00:00 2001 From: Joel Davis Date: Mon, 15 Jun 2015 09:23:57 -0700 Subject: [PATCH 1/3] iOS example working based on modified OpenGL3 example + Synergy --- examples/ios_example/.gitignore | 3 + examples/ios_example/README.md | 35 + .../imguiex.xcodeproj/project.pbxproj | 365 ++++++++ examples/ios_example/imguiex/AppDelegate.h | 13 + examples/ios_example/imguiex/AppDelegate.m | 41 + .../imguiex/Base.lproj/LaunchScreen.xib | 32 + .../imguiex/Base.lproj/Main.storyboard | 44 + .../ios_example/imguiex/GameViewController.h | 13 + .../ios_example/imguiex/GameViewController.m | 481 ++++++++++ .../AppIcon.appiconset/Contents.json | 72 ++ .../icon_imgui_60@2x~iphone.png | Bin 0 -> 4491 bytes .../icon_imgui_60@3x~iphone.png | Bin 0 -> 7022 bytes .../icon_imgui_76@2x~ipad.png | Bin 0 -> 5727 bytes .../AppIcon.appiconset/icon_imgui_76~ipad.png | Bin 0 -> 2838 bytes examples/ios_example/imguiex/Info.plist | 49 ++ .../ios_example/imguiex/Shaders/Shader.fsh | 10 + .../ios_example/imguiex/Shaders/Shader.vsh | 25 + examples/ios_example/imguiex/debug_hud.cpp | 53 ++ examples/ios_example/imguiex/debug_hud.h | 28 + .../ios_example/imguiex/imgui_ex_icon.png | Bin 0 -> 16394 bytes examples/ios_example/imguiex/imgui_impl_ios.h | 26 + .../ios_example/imguiex/imgui_impl_ios.mm | 825 ++++++++++++++++++ examples/ios_example/imguiex/main.m | 13 + examples/libs/usynergy/uSynergy.c | 636 ++++++++++++++ examples/libs/usynergy/uSynergy.h | 420 +++++++++ 25 files changed, 3184 insertions(+) create mode 100644 examples/ios_example/.gitignore create mode 100644 examples/ios_example/README.md create mode 100644 examples/ios_example/imguiex.xcodeproj/project.pbxproj create mode 100644 examples/ios_example/imguiex/AppDelegate.h create mode 100644 examples/ios_example/imguiex/AppDelegate.m create mode 100644 examples/ios_example/imguiex/Base.lproj/LaunchScreen.xib create mode 100644 examples/ios_example/imguiex/Base.lproj/Main.storyboard create mode 100644 examples/ios_example/imguiex/GameViewController.h create mode 100644 examples/ios_example/imguiex/GameViewController.m create mode 100644 examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_60@2x~iphone.png create mode 100644 examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_60@3x~iphone.png create mode 100644 examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_76@2x~ipad.png create mode 100644 examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_76~ipad.png create mode 100644 examples/ios_example/imguiex/Info.plist create mode 100644 examples/ios_example/imguiex/Shaders/Shader.fsh create mode 100644 examples/ios_example/imguiex/Shaders/Shader.vsh create mode 100644 examples/ios_example/imguiex/debug_hud.cpp create mode 100644 examples/ios_example/imguiex/debug_hud.h create mode 100644 examples/ios_example/imguiex/imgui_ex_icon.png create mode 100644 examples/ios_example/imguiex/imgui_impl_ios.h create mode 100644 examples/ios_example/imguiex/imgui_impl_ios.mm create mode 100644 examples/ios_example/imguiex/main.m create mode 100644 examples/libs/usynergy/uSynergy.c create mode 100644 examples/libs/usynergy/uSynergy.h diff --git a/examples/ios_example/.gitignore b/examples/ios_example/.gitignore new file mode 100644 index 000000000..8feda89d3 --- /dev/null +++ b/examples/ios_example/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +imguiex.xcodeproj/project.xcworkspace/ +imguiex.xcodeproj/xcuserdata/ \ No newline at end of file diff --git a/examples/ios_example/README.md b/examples/ios_example/README.md new file mode 100644 index 000000000..fc179f0d0 --- /dev/null +++ b/examples/ios_example/README.md @@ -0,0 +1,35 @@ +# iOS example + +---- +## Introduction + +This example is the default XCode "OpenGL" example code, modified to support IMGUI and [Synergy](http://synergy-project.org/). + +Synergy (remote keyboard/mouse) is not required, but it's pretty hard to use IMGUI without it. Synergy includes a "uSynergy" library that allows embedding a synergy client, this is what is used here. IMGUI supports "Touch Padding", and this is enabled when Synergy is not active. + +## How to Use +---- + +0. In Synergy, go to Preferences, and uncheck "Use SSL encryption" +0. Run the example app. +0. Tap the "servername" button in the corner +0. Enter the name or the IP of your synergy host +0. If you had previously connected to a server, you may need to kill and re-start the app. + +---- +## Notes and TODOs + +Things that would be nice but I didn't get around to doing: + +* iOS software keyboard not supported for text inputs +* iOS hardware (bluetooth) keyboards not supported +* Graceful disconnect/reconnect from uSynergy. +* Copy/Paste not well-supported + +---- +## C++ on iOS +IMGUI is a c++ library. If you want to include it directly, rename your Obj-C file to have the ".mm" extension. + +Alternatively, you can wrap your debug code in a C interface, this is what I am demonstrating here with the "debug_hud.h" interface. Either approach works, use whatever you prefer. + +In my case, most of my game code is already in C++ so it's not really an issue and I can use IMGUI directly. diff --git a/examples/ios_example/imguiex.xcodeproj/project.pbxproj b/examples/ios_example/imguiex.xcodeproj/project.pbxproj new file mode 100644 index 000000000..5e9b015f8 --- /dev/null +++ b/examples/ios_example/imguiex.xcodeproj/project.pbxproj @@ -0,0 +1,365 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 6D1E39171B35EEF10017B40F /* uSynergy.c in Sources */ = {isa = PBXBuildFile; fileRef = 6D1E39151B35EEF10017B40F /* uSynergy.c */; }; + 6D2FC55A1B2E632000C130BA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D2FC5591B2E632000C130BA /* main.m */; }; + 6D2FC55D1B2E632000C130BA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D2FC55C1B2E632000C130BA /* AppDelegate.m */; }; + 6D2FC55F1B2E632000C130BA /* Shader.fsh in Resources */ = {isa = PBXBuildFile; fileRef = 6D2FC55E1B2E632000C130BA /* Shader.fsh */; }; + 6D2FC5611B2E632000C130BA /* Shader.vsh in Resources */ = {isa = PBXBuildFile; fileRef = 6D2FC5601B2E632000C130BA /* Shader.vsh */; }; + 6D2FC5641B2E632000C130BA /* GameViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D2FC5631B2E632000C130BA /* GameViewController.m */; }; + 6D2FC5671B2E632000C130BA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D2FC5651B2E632000C130BA /* Main.storyboard */; }; + 6D2FC5691B2E632000C130BA /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2FC5681B2E632000C130BA /* Images.xcassets */; }; + 6D2FC56C1B2E632000C130BA /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6D2FC56A1B2E632000C130BA /* LaunchScreen.xib */; }; + 6D2FC5831B2E63A100C130BA /* imgui_impl_ios.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6D2FC5811B2E63A100C130BA /* imgui_impl_ios.mm */; }; + 6D2FC5881B2E64AB00C130BA /* imgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D2FC5861B2E64AB00C130BA /* imgui.cpp */; }; + 6D2FC58B1B2E6A5500C130BA /* debug_hud.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6D2FC5891B2E6A5500C130BA /* debug_hud.cpp */; }; + 6D2FC5911B30773F00C130BA /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D2FC5901B30773F00C130BA /* CFNetwork.framework */; }; + 6D2FC5931B30774900C130BA /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D2FC5921B30774900C130BA /* SystemConfiguration.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 6D1E39151B35EEF10017B40F /* uSynergy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = uSynergy.c; sourceTree = ""; }; + 6D1E39161B35EEF10017B40F /* uSynergy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uSynergy.h; sourceTree = ""; }; + 6D2FC5541B2E632000C130BA /* imguiex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = imguiex.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6D2FC5581B2E632000C130BA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6D2FC5591B2E632000C130BA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 6D2FC55B1B2E632000C130BA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 6D2FC55C1B2E632000C130BA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 6D2FC55E1B2E632000C130BA /* Shader.fsh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.glsl; name = Shader.fsh; path = Shaders/Shader.fsh; sourceTree = ""; }; + 6D2FC5601B2E632000C130BA /* Shader.vsh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.glsl; name = Shader.vsh; path = Shaders/Shader.vsh; sourceTree = ""; }; + 6D2FC5621B2E632000C130BA /* GameViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GameViewController.h; sourceTree = ""; }; + 6D2FC5631B2E632000C130BA /* GameViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GameViewController.m; sourceTree = ""; }; + 6D2FC5661B2E632000C130BA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 6D2FC5681B2E632000C130BA /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 6D2FC56B1B2E632000C130BA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 6D2FC5811B2E63A100C130BA /* imgui_impl_ios.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = imgui_impl_ios.mm; sourceTree = ""; }; + 6D2FC5821B2E63A100C130BA /* imgui_impl_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = imgui_impl_ios.h; sourceTree = ""; }; + 6D2FC5851B2E64AB00C130BA /* imconfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imconfig.h; path = ../../imconfig.h; sourceTree = ""; }; + 6D2FC5861B2E64AB00C130BA /* imgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = imgui.cpp; path = ../../imgui.cpp; sourceTree = ""; }; + 6D2FC5871B2E64AB00C130BA /* imgui.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = imgui.h; path = ../../imgui.h; sourceTree = ""; }; + 6D2FC5891B2E6A5500C130BA /* debug_hud.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = debug_hud.cpp; sourceTree = ""; }; + 6D2FC58A1B2E6A5500C130BA /* debug_hud.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug_hud.h; sourceTree = ""; }; + 6D2FC5901B30773F00C130BA /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + 6D2FC5921B30774900C130BA /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6D2FC5511B2E632000C130BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6D2FC5931B30774900C130BA /* SystemConfiguration.framework in Frameworks */, + 6D2FC5911B30773F00C130BA /* CFNetwork.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 6D1E39141B35EEF10017B40F /* usynergy */ = { + isa = PBXGroup; + children = ( + 6D1E39151B35EEF10017B40F /* uSynergy.c */, + 6D1E39161B35EEF10017B40F /* uSynergy.h */, + ); + name = usynergy; + path = ../libs/usynergy; + sourceTree = ""; + }; + 6D2FC54B1B2E632000C130BA = { + isa = PBXGroup; + children = ( + 6D1E39141B35EEF10017B40F /* usynergy */, + 6D2FC5841B2E648D00C130BA /* imgui */, + 6D2FC5561B2E632000C130BA /* imguiex */, + 6D2FC5551B2E632000C130BA /* Products */, + ); + sourceTree = ""; + }; + 6D2FC5551B2E632000C130BA /* Products */ = { + isa = PBXGroup; + children = ( + 6D2FC5541B2E632000C130BA /* imguiex.app */, + ); + name = Products; + sourceTree = ""; + }; + 6D2FC5561B2E632000C130BA /* imguiex */ = { + isa = PBXGroup; + children = ( + 6D2FC5811B2E63A100C130BA /* imgui_impl_ios.mm */, + 6D2FC5821B2E63A100C130BA /* imgui_impl_ios.h */, + 6D2FC5891B2E6A5500C130BA /* debug_hud.cpp */, + 6D2FC58A1B2E6A5500C130BA /* debug_hud.h */, + 6D2FC55B1B2E632000C130BA /* AppDelegate.h */, + 6D2FC55C1B2E632000C130BA /* AppDelegate.m */, + 6D2FC55E1B2E632000C130BA /* Shader.fsh */, + 6D2FC5601B2E632000C130BA /* Shader.vsh */, + 6D2FC5621B2E632000C130BA /* GameViewController.h */, + 6D2FC5631B2E632000C130BA /* GameViewController.m */, + 6D2FC5651B2E632000C130BA /* Main.storyboard */, + 6D2FC5681B2E632000C130BA /* Images.xcassets */, + 6D2FC56A1B2E632000C130BA /* LaunchScreen.xib */, + 6D2FC5571B2E632000C130BA /* Supporting Files */, + ); + path = imguiex; + sourceTree = ""; + }; + 6D2FC5571B2E632000C130BA /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 6D2FC5921B30774900C130BA /* SystemConfiguration.framework */, + 6D2FC5901B30773F00C130BA /* CFNetwork.framework */, + 6D2FC5581B2E632000C130BA /* Info.plist */, + 6D2FC5591B2E632000C130BA /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 6D2FC5841B2E648D00C130BA /* imgui */ = { + isa = PBXGroup; + children = ( + 6D2FC5851B2E64AB00C130BA /* imconfig.h */, + 6D2FC5861B2E64AB00C130BA /* imgui.cpp */, + 6D2FC5871B2E64AB00C130BA /* imgui.h */, + ); + name = imgui; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6D2FC5531B2E632000C130BA /* imguiex */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6D2FC57B1B2E632000C130BA /* Build configuration list for PBXNativeTarget "imguiex" */; + buildPhases = ( + 6D2FC5501B2E632000C130BA /* Sources */, + 6D2FC5511B2E632000C130BA /* Frameworks */, + 6D2FC5521B2E632000C130BA /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = imguiex; + productName = imguiex; + productReference = 6D2FC5541B2E632000C130BA /* imguiex.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6D2FC54C1B2E632000C130BA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + ORGANIZATIONNAME = "Joel Davis"; + TargetAttributes = { + 6D2FC5531B2E632000C130BA = { + CreatedOnToolsVersion = 6.3.2; + }; + }; + }; + buildConfigurationList = 6D2FC54F1B2E632000C130BA /* Build configuration list for PBXProject "imguiex" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6D2FC54B1B2E632000C130BA; + productRefGroup = 6D2FC5551B2E632000C130BA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6D2FC5531B2E632000C130BA /* imguiex */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6D2FC5521B2E632000C130BA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6D2FC56C1B2E632000C130BA /* LaunchScreen.xib in Resources */, + 6D2FC5671B2E632000C130BA /* Main.storyboard in Resources */, + 6D2FC5691B2E632000C130BA /* Images.xcassets in Resources */, + 6D2FC5611B2E632000C130BA /* Shader.vsh in Resources */, + 6D2FC55F1B2E632000C130BA /* Shader.fsh in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6D2FC5501B2E632000C130BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6D2FC55D1B2E632000C130BA /* AppDelegate.m in Sources */, + 6D2FC5831B2E63A100C130BA /* imgui_impl_ios.mm in Sources */, + 6D1E39171B35EEF10017B40F /* uSynergy.c in Sources */, + 6D2FC5641B2E632000C130BA /* GameViewController.m in Sources */, + 6D2FC55A1B2E632000C130BA /* main.m in Sources */, + 6D2FC5881B2E64AB00C130BA /* imgui.cpp in Sources */, + 6D2FC58B1B2E6A5500C130BA /* debug_hud.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 6D2FC5651B2E632000C130BA /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6D2FC5661B2E632000C130BA /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 6D2FC56A1B2E632000C130BA /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 6D2FC56B1B2E632000C130BA /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 6D2FC5791B2E632000C130BA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6D2FC57A1B2E632000C130BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6D2FC57C1B2E632000C130BA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = imguiex/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 6D2FC57D1B2E632000C130BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = imguiex/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6D2FC54F1B2E632000C130BA /* Build configuration list for PBXProject "imguiex" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6D2FC5791B2E632000C130BA /* Debug */, + 6D2FC57A1B2E632000C130BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6D2FC57B1B2E632000C130BA /* Build configuration list for PBXNativeTarget "imguiex" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6D2FC57C1B2E632000C130BA /* Debug */, + 6D2FC57D1B2E632000C130BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6D2FC54C1B2E632000C130BA /* Project object */; +} diff --git a/examples/ios_example/imguiex/AppDelegate.h b/examples/ios_example/imguiex/AppDelegate.h new file mode 100644 index 000000000..82f1542eb --- /dev/null +++ b/examples/ios_example/imguiex/AppDelegate.h @@ -0,0 +1,13 @@ +// +// AppDelegate.h +// imguiex + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/examples/ios_example/imguiex/AppDelegate.m b/examples/ios_example/imguiex/AppDelegate.m new file mode 100644 index 000000000..ab83101e7 --- /dev/null +++ b/examples/ios_example/imguiex/AppDelegate.m @@ -0,0 +1,41 @@ +// +// AppDelegate.m +// imguiex + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/examples/ios_example/imguiex/Base.lproj/LaunchScreen.xib b/examples/ios_example/imguiex/Base.lproj/LaunchScreen.xib new file mode 100644 index 000000000..5717c00e2 --- /dev/null +++ b/examples/ios_example/imguiex/Base.lproj/LaunchScreen.xib @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios_example/imguiex/Base.lproj/Main.storyboard b/examples/ios_example/imguiex/Base.lproj/Main.storyboard new file mode 100644 index 000000000..90dfb2e67 --- /dev/null +++ b/examples/ios_example/imguiex/Base.lproj/Main.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ios_example/imguiex/GameViewController.h b/examples/ios_example/imguiex/GameViewController.h new file mode 100644 index 000000000..7c7eeeae5 --- /dev/null +++ b/examples/ios_example/imguiex/GameViewController.h @@ -0,0 +1,13 @@ +// +// GameViewController.h +// imguiex + +// This is the OpenGL Example template from XCode, modified to +// support IMGUI + +#import +#import + +@interface GameViewController : GLKViewController + +@end diff --git a/examples/ios_example/imguiex/GameViewController.m b/examples/ios_example/imguiex/GameViewController.m new file mode 100644 index 000000000..e902f7ac2 --- /dev/null +++ b/examples/ios_example/imguiex/GameViewController.m @@ -0,0 +1,481 @@ +// +// GameViewController.m +// imguiex +// +#import "GameViewController.h" +#import + +#import "imgui_impl_ios.h" +#import "debug_hud.h" + +#define BUFFER_OFFSET(i) ((char *)NULL + (i)) + +#define SERVERNAME_KEY @"ServerName" + +#define SERVERNAME_ALERT_TAG (10) + +// Uniform index. +enum +{ + UNIFORM_MODELVIEWPROJECTION_MATRIX, + UNIFORM_NORMAL_MATRIX, + UNIFORM_DIFFUSE_COLOR, + + NUM_UNIFORMS +}; +GLint uniforms[NUM_UNIFORMS]; + +// Attribute index. +enum +{ + ATTRIB_VERTEX, + ATTRIB_NORMAL, + NUM_ATTRIBUTES +}; + +GLfloat gCubeVertexData[216] = +{ + // Data layout for each line below is: + // positionX, positionY, positionZ, normalX, normalY, normalZ, + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, + + 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + + -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, + + -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, + + 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + + 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f +}; + +@interface GameViewController () +{ + GLuint _program; + + GLKMatrix4 _modelViewProjectionMatrix; + GLKMatrix3 _normalMatrix; + float _rotation; + + GLuint _vertexArray; + GLuint _vertexBuffer; + + DebugHUD _hud; +} +@property (strong, nonatomic) EAGLContext *context; +@property (strong, nonatomic) GLKBaseEffect *effect; +@property (strong, nonatomic) ImGuiHelper *imgui; +@property (weak, nonatomic) IBOutlet UIButton *btnServername; + +@property (strong, nonatomic) NSString *serverName; + +- (IBAction)onServernameTapped:(id)sender; + +- (void)setupGL; +- (void)tearDownGL; + +- (BOOL)loadShaders; +- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file; +- (BOOL)linkProgram:(GLuint)prog; +- (BOOL)validateProgram:(GLuint)prog; +@end + +@implementation GameViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + + if (!self.context) { + NSLog(@"Failed to create ES context"); + } + + GLKView *view = (GLKView *)self.view; + view.context = self.context; + view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + + [self.btnServername setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + + [self setupGL]; + + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + self.serverName = [userDefaults objectForKey: SERVERNAME_KEY ]; + self.imgui = [[ImGuiHelper alloc] initWithView:self.view ]; + if (self.serverName) + { + [self.btnServername setTitle:self.serverName forState:UIControlStateNormal]; + [self.imgui connectServer: self.serverName ]; + } + + DebugHUD_InitDefaults( &_hud ); +} + +- (void)dealloc +{ + [self tearDownGL]; + + if ([EAGLContext currentContext] == self.context) { + [EAGLContext setCurrentContext:nil]; + } +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + + if ([self isViewLoaded] && ([[self view] window] == nil)) { + self.view = nil; + + [self tearDownGL]; + + if ([EAGLContext currentContext] == self.context) { + [EAGLContext setCurrentContext:nil]; + } + self.context = nil; + } + + // Dispose of any resources that can be recreated. +} + + +- (BOOL)prefersStatusBarHidden { + return YES; +} + +- (IBAction)onServernameTapped:(id)sender +{ + UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Set Server" message:@"Enter server name or IP for uSynergy" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"Cancel", nil ]; + alert.alertViewStyle = UIAlertViewStylePlainTextInput; + alert.tag = SERVERNAME_ALERT_TAG; // cheezy way to tell which alert view we're responding to + [alert show]; +} + +- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + if ((buttonIndex==0)&&(alertView.tag==SERVERNAME_ALERT_TAG)) + { + // This is really janky. I usually just hardcode the servername since I'm building it anyway. + // If you want to properly handle updating the server, you'll want to tear down and recreate + // the usynergy stuff in connectServer + BOOL serverNameWasSet = self.serverName.length > 0; + NSString *serverName = [[alertView textFieldAtIndex:0] text]; + + if ([serverName length] > 0) { + self.serverName = serverName; + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setObject:serverName forKey:SERVERNAME_KEY ]; + [userDefaults synchronize]; + + [self.btnServername setTitle:self.serverName forState:UIControlStateNormal]; + + // If we hadn't previously connected, try now + if (!serverNameWasSet) { + [self.imgui connectServer:self.serverName]; + } + else + { + UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Servername Updated" + message:@"Restart the app to connect the server" + delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil]; + [alert show]; + } + } + } +} + +- (void)setupGL +{ + [EAGLContext setCurrentContext:self.context]; + + [self loadShaders]; + + self.effect = [[GLKBaseEffect alloc] init]; + self.effect.light0.enabled = GL_TRUE; + self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f, 1.0f); + + glEnable(GL_DEPTH_TEST); + + glGenVertexArraysOES(1, &_vertexArray); + glBindVertexArrayOES(_vertexArray); + + glGenBuffers(1, &_vertexBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData), gCubeVertexData, GL_STATIC_DRAW); + + glEnableVertexAttribArray(GLKVertexAttribPosition); + glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0)); + glEnableVertexAttribArray(GLKVertexAttribNormal); + glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12)); + + glBindVertexArrayOES(0); + + +} + +- (void)tearDownGL +{ + [EAGLContext setCurrentContext:self.context]; + + glDeleteBuffers(1, &_vertexBuffer); + glDeleteVertexArraysOES(1, &_vertexArray); + + self.effect = nil; + + if (_program) { + glDeleteProgram(_program); + _program = 0; + } +} + +#pragma mark - GLKView and GLKViewController delegate methods + +- (void)update +{ + float aspect = fabs(self.view.bounds.size.width / self.view.bounds.size.height); + GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 0.1f, 100.0f); + + self.effect.transform.projectionMatrix = projectionMatrix; + + GLKMatrix4 baseModelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -4.0f); + baseModelViewMatrix = GLKMatrix4Rotate(baseModelViewMatrix, _rotation, 0.0f, 1.0f, 0.0f); + + // Compute the model view matrix for the object rendered with GLKit + GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -1.5f); + modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f); + modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix); + + self.effect.transform.modelviewMatrix = modelViewMatrix; + + // Compute the model view matrix for the object rendered with ES2 + modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, 1.5f); + modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, _rotation, 1.0f, 1.0f, 1.0f); + modelViewMatrix = GLKMatrix4Multiply(baseModelViewMatrix, modelViewMatrix); + + _normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL); + + _modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix); + + _rotation += self.timeSinceLastUpdate * (_hud.rotation_speed * (M_PI / 180.0)); +} + + +- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect +{ + glClearColor(0.65f, 0.65f, 0.65f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glBindVertexArrayOES(_vertexArray); + + // Render the object with GLKit + [self.effect prepareToDraw]; + + glDrawArrays(GL_TRIANGLES, 0, 36); + + // Render the object again with ES2 + glUseProgram(_program); + + glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, 0, _modelViewProjectionMatrix.m); + glUniformMatrix3fv(uniforms[UNIFORM_NORMAL_MATRIX], 1, 0, _normalMatrix.m); + glUniform3f(uniforms[UNIFORM_DIFFUSE_COLOR], _hud.cubeColor1[0], _hud.cubeColor1[1], _hud.cubeColor1[2] ); + + glDrawArrays(GL_TRIANGLES, 0, 36); + + [self.imgui newFrame]; + + // Now do our ImGUI UI + DebugHUD_DoInterface( &_hud ); + + self.effect.light0.diffuseColor = GLKVector4Make( _hud.cubeColor2[0], _hud.cubeColor2[1], _hud.cubeColor2[2], 1.0f); + + // Now render Imgui + [self.imgui render]; + +} + +#pragma mark - OpenGL ES 2 shader compilation + +- (BOOL)loadShaders +{ + GLuint vertShader, fragShader; + NSString *vertShaderPathname, *fragShaderPathname; + + // Create shader program. + _program = glCreateProgram(); + + // Create and compile vertex shader. + vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"]; + if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) { + NSLog(@"Failed to compile vertex shader"); + return NO; + } + + // Create and compile fragment shader. + fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"]; + if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) { + NSLog(@"Failed to compile fragment shader"); + return NO; + } + + // Attach vertex shader to program. + glAttachShader(_program, vertShader); + + // Attach fragment shader to program. + glAttachShader(_program, fragShader); + + // Bind attribute locations. + // This needs to be done prior to linking. + glBindAttribLocation(_program, GLKVertexAttribPosition, "position"); + glBindAttribLocation(_program, GLKVertexAttribNormal, "normal"); + + // Link program. + if (![self linkProgram:_program]) { + NSLog(@"Failed to link program: %d", _program); + + if (vertShader) { + glDeleteShader(vertShader); + vertShader = 0; + } + if (fragShader) { + glDeleteShader(fragShader); + fragShader = 0; + } + if (_program) { + glDeleteProgram(_program); + _program = 0; + } + + return NO; + } + + // Get uniform locations. + uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX] = glGetUniformLocation(_program, "modelViewProjectionMatrix"); + uniforms[UNIFORM_NORMAL_MATRIX] = glGetUniformLocation(_program, "normalMatrix"); + uniforms[UNIFORM_DIFFUSE_COLOR] = glGetUniformLocation(_program, "diffuseColor"); + + // Release vertex and fragment shaders. + if (vertShader) { + glDetachShader(_program, vertShader); + glDeleteShader(vertShader); + } + if (fragShader) { + glDetachShader(_program, fragShader); + glDeleteShader(fragShader); + } + + return YES; +} + +- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file +{ + GLint status; + const GLchar *source; + + source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String]; + if (!source) { + NSLog(@"Failed to load vertex shader"); + return NO; + } + + *shader = glCreateShader(type); + glShaderSource(*shader, 1, &source, NULL); + glCompileShader(*shader); + +#if defined(DEBUG) + GLint logLength; + glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetShaderInfoLog(*shader, logLength, &logLength, log); + NSLog(@"Shader compile log:\n%s", log); + free(log); + } +#endif + + glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); + if (status == 0) { + glDeleteShader(*shader); + return NO; + } + + return YES; +} + +- (BOOL)linkProgram:(GLuint)prog +{ + GLint status; + glLinkProgram(prog); + +#if defined(DEBUG) + GLint logLength; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetProgramInfoLog(prog, logLength, &logLength, log); + NSLog(@"Program link log:\n%s", log); + free(log); + } +#endif + + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (status == 0) { + return NO; + } + + return YES; +} + +- (BOOL)validateProgram:(GLuint)prog +{ + GLint logLength, status; + + glValidateProgram(prog); + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetProgramInfoLog(prog, logLength, &logLength, log); + NSLog(@"Program validate log:\n%s", log); + free(log); + } + + glGetProgramiv(prog, GL_VALIDATE_STATUS, &status); + if (status == 0) { + return NO; + } + + return YES; +} + +@end diff --git a/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a05a29bbf --- /dev/null +++ b/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,72 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon_imgui_60@2x~iphone.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon_imgui_60@3x~iphone.png", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon_imgui_76~ipad.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "icon_imgui_76@2x~ipad.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_60@2x~iphone.png b/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_60@2x~iphone.png new file mode 100644 index 0000000000000000000000000000000000000000..0b154f349e126ce471a1f78a6ce98899084932e0 GIT binary patch literal 4491 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 dVG5lg{vR_(WKfP$Ob6yB%e$|@` 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 sJ|_r$|CL0PfI(WQQbd|`>Cy=(NDI<} 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 literal 0 HcmV?d00001 diff --git a/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_76~ipad.png b/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_76~ipad.png new file mode 100644 index 0000000000000000000000000000000000000000..8813551e6bf63329171151e62cc080f72d82c87d GIT binary patch literal 2838 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 + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.imgui.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ios_example/imguiex/Shaders/Shader.fsh b/examples/ios_example/imguiex/Shaders/Shader.fsh new file mode 100644 index 000000000..4000524fc --- /dev/null +++ b/examples/ios_example/imguiex/Shaders/Shader.fsh @@ -0,0 +1,10 @@ +// +// Shader.fsh +// imguiex + +varying lowp vec4 colorVarying; + +void main() +{ + gl_FragColor = colorVarying; +} diff --git a/examples/ios_example/imguiex/Shaders/Shader.vsh b/examples/ios_example/imguiex/Shaders/Shader.vsh new file mode 100644 index 000000000..313c3d7a3 --- /dev/null +++ b/examples/ios_example/imguiex/Shaders/Shader.vsh @@ -0,0 +1,25 @@ +// +// Shader.vsh +// imguiex + +attribute vec4 position; +attribute vec3 normal; + +varying lowp vec4 colorVarying; + +uniform vec3 diffuseColor; +uniform mat4 modelViewProjectionMatrix; +uniform mat3 normalMatrix; + +void main() +{ + vec3 eyeNormal = normalize(normalMatrix * normal); + vec3 lightPosition = vec3(0.0, 0.0, 1.0); + + float nDotVP = max(0.0, dot(eyeNormal, normalize(lightPosition))); + + vec3 colorLit = diffuseColor * nDotVP; + colorVarying = vec4( colorLit.x, colorLit.y, colorLit.z, 1.0 ); + + gl_Position = modelViewProjectionMatrix * position; +} diff --git a/examples/ios_example/imguiex/debug_hud.cpp b/examples/ios_example/imguiex/debug_hud.cpp new file mode 100644 index 000000000..5d364d7b4 --- /dev/null +++ b/examples/ios_example/imguiex/debug_hud.cpp @@ -0,0 +1,53 @@ +// +// debug_hud.cpp +// imguiex + +#include + +#include "debug_hud.h" +#include "imgui.h" + +void DebugHUD_InitDefaults( DebugHUD *hud ) +{ + hud->show_test_window = 1; + hud->show_example_window = 1; + hud->rotation_speed = 15.0; + + hud->cubeColor1[0] = 0.4; + hud->cubeColor1[1] = 0.4; + hud->cubeColor1[2] = 1.0; + hud->cubeColor1[3] = 1.0; + + hud->cubeColor2[0] = 1.0; + hud->cubeColor2[1] = 0.4; + hud->cubeColor2[2] = 0.4; + hud->cubeColor2[3] = 1.0; + +} + +void DebugHUD_DoInterface( DebugHUD *hud ) +{ + if (hud->show_test_window) + { + ImGui::SetNextWindowPos( ImVec2( 400, 20 ), ImGuiSetCond_FirstUseEver ); + bool show_test_window = hud->show_test_window; + ImGui::ShowTestWindow( &show_test_window ); + hud->show_test_window = show_test_window; + } + + if (hud->show_example_window) + { + bool show_window = hud->show_example_window; + ImGui::SetNextWindowPos( ImVec2( 20, 20 ), ImGuiSetCond_FirstUseEver ); + ImGui::SetNextWindowSize( ImVec2( 350, 200 ), ImGuiSetCond_FirstUseEver ); + ImGui::Begin("Another Window", &show_window); + hud->show_example_window = show_window; + + ImGui::ColorEdit3("Cube 1 Color", hud->cubeColor1); + ImGui::ColorEdit3("Cube 2 Color", hud->cubeColor2); + ImGui::SliderFloat("Rotation Speed", &(hud->rotation_speed), 0.0f, 200.0f); + + ImGui::End(); + } + +} \ No newline at end of file diff --git a/examples/ios_example/imguiex/debug_hud.h b/examples/ios_example/imguiex/debug_hud.h new file mode 100644 index 000000000..6df9ff0cb --- /dev/null +++ b/examples/ios_example/imguiex/debug_hud.h @@ -0,0 +1,28 @@ +// +// debug_hud.h +// imguiex + +#ifndef __imguiex__debug_hud__ +#define __imguiex__debug_hud__ + +typedef struct DebugHUD +{ + int show_test_window; + int show_example_window; + float rotation_speed; + float cubeColor1[4]; + float cubeColor2[4]; +} DebugHUD; + +#if __cplusplus +extern "C" { +#endif + +void DebugHUD_InitDefaults( DebugHUD *hud ); +void DebugHUD_DoInterface( DebugHUD *hud ); + +#if __cplusplus +} +#endif + +#endif /* defined(__imguiex__debug_hud__) */ diff --git a/examples/ios_example/imguiex/imgui_ex_icon.png b/examples/ios_example/imguiex/imgui_ex_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c83d2e777ea9b9b0bfd78e7c2f9787de7de59d5a GIT binary patch literal 16394 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 +#include + +@interface ImGuiHelper : NSObject + +- (id) initWithView: (UIView *)view; + +- (void)connectServer: (NSString*)serverName; + +- (void)render; +- (void)newFrame; + +@end + + +#endif /* defined(__imguiex__imgui_impl_ios__) */ diff --git a/examples/ios_example/imguiex/imgui_impl_ios.mm b/examples/ios_example/imguiex/imgui_impl_ios.mm new file mode 100644 index 000000000..c7cf41dfb --- /dev/null +++ b/examples/ios_example/imguiex/imgui_impl_ios.mm @@ -0,0 +1,825 @@ +// +// imgui_impl_ios.cpp +// imguiex + +#import +#import + +#include +#include +#include +#include + +#include "imgui_impl_ios.h" +#include "imgui.h" + +#include "uSynergy.h" + +// From Carbon HIToolbox/Events.h +enum { + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_ANSI_Grave = 0x32, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C +}; + +/* keycodes for keys that are independent of keyboard layout*/ +enum { + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; + +static char g_keycodeCharUnshifted[256] = {}; +static char g_keycodeCharShifted[256] = {}; + +//static double g_Time = 0.0f; +static bool g_MousePressed[3] = { false, false, false }; +static float g_mouseWheelX = 0.0f; +static float g_mouseWheelY = 0.0f; + +static GLuint g_FontTexture = 0; +static int g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0; +static int g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0; +static int g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0; +static size_t g_VboSize = 0; +static unsigned int g_VboHandle = 0, g_VaoHandle = 0; +static float g_displayScale; + +static int usynergy_sockfd; +static bool g_synergyPtrActive = false; +static uint16_t g_mousePosX = 0; +static uint16_t g_mousePosY = 0; + +static void ImGui_ImplIOS_RenderDrawLists (ImDrawList** const cmd_lists, int cmd_lists_count); +bool ImGui_ImplIOS_CreateDeviceObjects(); + +static NSString *g_serverName; + +uSynergyBool ImGui_ConnectFunc(uSynergyCookie cookie) +{ + // NOTE: You need to turn off "Use SSL Encryption" in Synergy preferences, since + // uSynergy does not support SSL. + + NSLog( @"Connect Func!"); + struct addrinfo hints, *res; + + // first, load up address structs with getaddrinfo(): + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever + hints.ai_socktype = SOCK_STREAM; + + // get server address + getaddrinfo([g_serverName UTF8String], "24800", &hints, &res); + + if (!res) + { + NSLog( @"Could not find server: %@", g_serverName ); + return USYNERGY_FALSE; + } + + // make a socket: + usynergy_sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + + // connect it to the address and port we passed in to getaddrinfo(): + int ret = connect(usynergy_sockfd, res->ai_addr, res->ai_addrlen); + if (!ret) { + NSLog( @"Connect suceeded..."); + } else { + NSLog( @"Connect failed, %d", ret ); + } + + + return USYNERGY_TRUE; +} + +uSynergyBool ImGui_SendFunc(uSynergyCookie cookie, const uint8_t *buffer, int length) +{ +// NSLog( @"Send Func" ); + send( usynergy_sockfd, buffer, length, 0 ); + + return USYNERGY_TRUE; +} + +uSynergyBool ImGui_RecvFunc(uSynergyCookie cookie, uint8_t *buffer, int maxLength, int* outLength) +{ + *outLength = (int)recv( usynergy_sockfd, buffer, maxLength, 0 ); + + return USYNERGY_TRUE; +} + +void ImGui_SleepFunc(uSynergyCookie cookie, int timeMs) +{ + usleep( timeMs * 1000 ); +} + +uint32_t ImGui_GetTimeFunc() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int32_t)((tv.tv_sec) * 1000 + (tv.tv_usec) / 1000); +} + +void ImGui_TraceFunc(uSynergyCookie cookie, const char *text) +{ + puts(text); +} + +void ImGui_ScreenActiveCallback(uSynergyCookie cookie, uSynergyBool active) +{ + g_synergyPtrActive = active; +// printf( "Synergy: screen activate %s\n", active?"YES":"NO" ); +} + +void ImGui_MouseCallback(uSynergyCookie cookie, uint16_t x, uint16_t y, int16_t wheelX, int16_t wheelY, + uSynergyBool buttonLeft, uSynergyBool buttonRight, uSynergyBool buttonMiddle) +{ +// printf("Synergy: mouse callback %d %d -- wheel %d %d\n", x, y, wheelX, wheelY ); + uSynergyContext *ctx = (uSynergyContext*)cookie; + g_mousePosX = x; + g_mousePosY = y; + g_mouseWheelX = wheelX; + g_mouseWheelY = wheelY; + g_MousePressed[0] = buttonLeft; + g_MousePressed[1] = buttonMiddle; + g_MousePressed[2] = buttonRight; + + ctx->m_mouseWheelX = 0; + ctx->m_mouseWheelY = 0; +} + +void ImGui_KeyboardCallback(uSynergyCookie cookie, uint16_t key, + uint16_t modifiers, uSynergyBool down, uSynergyBool repeat) +{ + int scanCode = key-1; +// printf("Synergy: keyboard callback: 0x%02X (%s)", scanCode, down?"true":"false"); + ImGuiIO& io = ImGui::GetIO(); + io.KeysDown[key] = down; + io.KeyShift = modifiers & USYNERGY_MODIFIER_SHIFT; + io.KeyCtrl = modifiers & USYNERGY_MODIFIER_CTRL; + io.KeyAlt = modifiers & USYNERGY_MODIFIER_ALT; + + + // Add this as keyboard input + if ((down) && (key) && (scanCode<256) && !(modifiers & USYNERGY_MODIFIER_CTRL)) { + int charForKeycode = 0; + if (modifiers & USYNERGY_MODIFIER_SHIFT ) { + charForKeycode = g_keycodeCharShifted[ scanCode ]; + } else { + charForKeycode = g_keycodeCharUnshifted[ scanCode ]; + } + + // If this key maps to a character input, apply it + io.AddInputCharacter((unsigned short)charForKeycode); + } + +} + +void ImGui_JoystickCallback(uSynergyCookie cookie, uint8_t joyNum, uint16_t buttons, int8_t leftStickX, int8_t leftStickY, int8_t rightStickX, int8_t rightStickY) +{ + printf("Synergy: joystick callback TODO\n"); +} + +void ImGui_ClipboardCallback(uSynergyCookie cookie, enum uSynergyClipboardFormat format, const uint8_t *data, uint32_t size) +{ + printf("Synergy: clipboard callback TODO\n" ); +} + + +@interface ImGuiHelper () +{ + BOOL _mouseDown; + BOOL _mouseTapped; + CGPoint _touchPos; + + uSynergyContext _synergyCtx; + dispatch_queue_t _synergyQueue; +} +@property (nonatomic, weak) UIView *view; +@property (nonatomic, strong) NSString *serverName; + +@end + +@implementation ImGuiHelper + +- (id) initWithView: (UIView *)view +{ + self = [super init]; + if (self) + { + self.view = view; + + [self setupImGuiHooks]; + } + return self; +} + +- (void)setupKeymaps +{ + // The keyboard mapping is a big headache. I tried for a while to find a better way to do this, + // but this was the best I could come up with. There are some device independent API's available + // to convert scan codes to unicode characters, but these are only available on mac and not + // on iOS as far as I can tell (it's part of Carbon). I didn't see any better way to do + // this or any way to get the character codes out of usynergy. + g_keycodeCharUnshifted[ kVK_ANSI_A ]='a'; + g_keycodeCharUnshifted[ kVK_ANSI_S ]='s'; + g_keycodeCharUnshifted[ kVK_ANSI_D ]='d'; + g_keycodeCharUnshifted[ kVK_ANSI_F ]='f'; + g_keycodeCharUnshifted[ kVK_ANSI_H ]='h'; + g_keycodeCharUnshifted[ kVK_ANSI_G ]='g'; + g_keycodeCharUnshifted[ kVK_ANSI_Z ]='z'; + g_keycodeCharUnshifted[ kVK_ANSI_X ]='x'; + g_keycodeCharUnshifted[ kVK_ANSI_C ]='c'; + g_keycodeCharUnshifted[ kVK_ANSI_V ]='v'; + g_keycodeCharUnshifted[ kVK_ANSI_B ]='b'; + g_keycodeCharUnshifted[ kVK_ANSI_Q ]='q'; + g_keycodeCharUnshifted[ kVK_ANSI_W ]='w'; + g_keycodeCharUnshifted[ kVK_ANSI_E ]='e'; + g_keycodeCharUnshifted[ kVK_ANSI_R ]='r'; + g_keycodeCharUnshifted[ kVK_ANSI_Y ]='y'; + g_keycodeCharUnshifted[ kVK_ANSI_T ]='t'; + g_keycodeCharUnshifted[ kVK_ANSI_1 ]='1'; + g_keycodeCharUnshifted[ kVK_ANSI_2 ]='2'; + g_keycodeCharUnshifted[ kVK_ANSI_3 ]='3'; + g_keycodeCharUnshifted[ kVK_ANSI_4 ]='4'; + g_keycodeCharUnshifted[ kVK_ANSI_6 ]='6'; + g_keycodeCharUnshifted[ kVK_ANSI_5 ]='5'; + g_keycodeCharUnshifted[ kVK_ANSI_Equal ]='='; + g_keycodeCharUnshifted[ kVK_ANSI_9 ]='9'; + g_keycodeCharUnshifted[ kVK_ANSI_7 ]='7'; + g_keycodeCharUnshifted[ kVK_ANSI_Minus ]='-'; + g_keycodeCharUnshifted[ kVK_ANSI_8 ]='8'; + g_keycodeCharUnshifted[ kVK_ANSI_0 ]='0'; + g_keycodeCharUnshifted[ kVK_ANSI_RightBracket ]=']'; + g_keycodeCharUnshifted[ kVK_ANSI_O ]='o'; + g_keycodeCharUnshifted[ kVK_ANSI_U ]='u'; + g_keycodeCharUnshifted[ kVK_ANSI_LeftBracket ]='['; + g_keycodeCharUnshifted[ kVK_ANSI_I ]='i'; + g_keycodeCharUnshifted[ kVK_ANSI_P ]='p'; + g_keycodeCharUnshifted[ kVK_ANSI_L ]='l'; + g_keycodeCharUnshifted[ kVK_ANSI_J ]='j'; + g_keycodeCharUnshifted[ kVK_ANSI_Quote ]='\''; + g_keycodeCharUnshifted[ kVK_ANSI_K ]='k'; + g_keycodeCharUnshifted[ kVK_ANSI_Semicolon ]=';'; + g_keycodeCharUnshifted[ kVK_ANSI_Backslash ]='\\'; + g_keycodeCharUnshifted[ kVK_ANSI_Comma ]=','; + g_keycodeCharUnshifted[ kVK_ANSI_Slash ]='/'; + g_keycodeCharUnshifted[ kVK_ANSI_N ]='n'; + g_keycodeCharUnshifted[ kVK_ANSI_M ]='m'; + g_keycodeCharUnshifted[ kVK_ANSI_Period ]='.'; + g_keycodeCharUnshifted[ kVK_ANSI_Grave ]='`'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadDecimal ]='.'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadMultiply ]='*'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadPlus ]='+'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadDivide ]='/'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadEnter ]='\n'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadMinus ]='-'; + g_keycodeCharUnshifted[ kVK_ANSI_KeypadEquals ]='='; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad0 ]='0'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad1 ]='1'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad2 ]='2'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad3 ]='3'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad4 ]='4'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad5 ]='5'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad6 ]='6'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad7 ]='7'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad8 ]='8'; + g_keycodeCharUnshifted[ kVK_ANSI_Keypad9 ]='9'; + g_keycodeCharUnshifted[ kVK_Space ]=' '; + + g_keycodeCharShifted[ kVK_ANSI_A ]='A'; + g_keycodeCharShifted[ kVK_ANSI_S ]='S'; + g_keycodeCharShifted[ kVK_ANSI_D ]='D'; + g_keycodeCharShifted[ kVK_ANSI_F ]='F'; + g_keycodeCharShifted[ kVK_ANSI_H ]='H'; + g_keycodeCharShifted[ kVK_ANSI_G ]='G'; + g_keycodeCharShifted[ kVK_ANSI_Z ]='Z'; + g_keycodeCharShifted[ kVK_ANSI_X ]='X'; + g_keycodeCharShifted[ kVK_ANSI_C ]='C'; + g_keycodeCharShifted[ kVK_ANSI_V ]='V'; + g_keycodeCharShifted[ kVK_ANSI_B ]='B'; + g_keycodeCharShifted[ kVK_ANSI_Q ]='Q'; + g_keycodeCharShifted[ kVK_ANSI_W ]='W'; + g_keycodeCharShifted[ kVK_ANSI_E ]='E'; + g_keycodeCharShifted[ kVK_ANSI_R ]='R'; + g_keycodeCharShifted[ kVK_ANSI_Y ]='Y'; + g_keycodeCharShifted[ kVK_ANSI_T ]='T'; + g_keycodeCharShifted[ kVK_ANSI_1 ]='!'; + g_keycodeCharShifted[ kVK_ANSI_2 ]='@'; + g_keycodeCharShifted[ kVK_ANSI_3 ]='#'; + g_keycodeCharShifted[ kVK_ANSI_4 ]='$'; + g_keycodeCharShifted[ kVK_ANSI_6 ]='^'; + g_keycodeCharShifted[ kVK_ANSI_5 ]='%'; + g_keycodeCharShifted[ kVK_ANSI_Equal ]='+'; + g_keycodeCharShifted[ kVK_ANSI_9 ]='('; + g_keycodeCharShifted[ kVK_ANSI_7 ]='&'; + g_keycodeCharShifted[ kVK_ANSI_Minus ]='_'; + g_keycodeCharShifted[ kVK_ANSI_8 ]='*'; + g_keycodeCharShifted[ kVK_ANSI_0 ]=')'; + g_keycodeCharShifted[ kVK_ANSI_RightBracket ]='}'; + g_keycodeCharShifted[ kVK_ANSI_O ]='O'; + g_keycodeCharShifted[ kVK_ANSI_U ]='U'; + g_keycodeCharShifted[ kVK_ANSI_LeftBracket ]='{'; + g_keycodeCharShifted[ kVK_ANSI_I ]='I'; + g_keycodeCharShifted[ kVK_ANSI_P ]='P'; + g_keycodeCharShifted[ kVK_ANSI_L ]='L'; + g_keycodeCharShifted[ kVK_ANSI_J ]='J'; + g_keycodeCharShifted[ kVK_ANSI_Quote ]='\"'; + g_keycodeCharShifted[ kVK_ANSI_K ]='K'; + g_keycodeCharShifted[ kVK_ANSI_Semicolon ]=':'; + g_keycodeCharShifted[ kVK_ANSI_Backslash ]='|'; + g_keycodeCharShifted[ kVK_ANSI_Comma ]='<'; + g_keycodeCharShifted[ kVK_ANSI_Slash ]='?'; + g_keycodeCharShifted[ kVK_ANSI_N ]='N'; + g_keycodeCharShifted[ kVK_ANSI_M ]='M'; + g_keycodeCharShifted[ kVK_ANSI_Period ]='>'; + g_keycodeCharShifted[ kVK_ANSI_Grave ]='~'; + g_keycodeCharShifted[ kVK_ANSI_KeypadDecimal ]='.'; + g_keycodeCharShifted[ kVK_ANSI_KeypadMultiply ]='*'; + g_keycodeCharShifted[ kVK_ANSI_KeypadPlus ]='+'; + g_keycodeCharShifted[ kVK_ANSI_KeypadDivide ]='/'; + g_keycodeCharShifted[ kVK_ANSI_KeypadEnter ]='\n'; + g_keycodeCharShifted[ kVK_ANSI_KeypadMinus ]='-'; + g_keycodeCharShifted[ kVK_ANSI_KeypadEquals ]='='; + g_keycodeCharShifted[ kVK_ANSI_Keypad0 ]='0'; + g_keycodeCharShifted[ kVK_ANSI_Keypad1 ]='1'; + g_keycodeCharShifted[ kVK_ANSI_Keypad2 ]='2'; + g_keycodeCharShifted[ kVK_ANSI_Keypad3 ]='3'; + g_keycodeCharShifted[ kVK_ANSI_Keypad4 ]='4'; + g_keycodeCharShifted[ kVK_ANSI_Keypad5 ]='5'; + g_keycodeCharShifted[ kVK_ANSI_Keypad6 ]='6'; + g_keycodeCharShifted[ kVK_ANSI_Keypad7 ]='7'; + g_keycodeCharShifted[ kVK_ANSI_Keypad8 ]='8'; + g_keycodeCharShifted[ kVK_ANSI_Keypad9 ]='9'; + g_keycodeCharShifted[ kVK_Space ]=' '; +} + +- (void)setupImGuiHooks +{ + ImGuiIO &io = ImGui::GetIO(); + + [self setupKeymaps]; + + // Account for retina display for glScissor + g_displayScale = [[UIScreen mainScreen] scale]; + + ImGuiStyle &style = ImGui::GetStyle(); + style.TouchExtraPadding = ImVec2( 4.0, 4.0 ); + + io.RenderDrawListsFn = ImGui_ImplIOS_RenderDrawLists; + + UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(viewDidPan:) ]; + [self.view addGestureRecognizer:panRecognizer]; + + UITapGestureRecognizer *tapRecoginzer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector( viewDidTap:)]; + [self.view addGestureRecognizer:tapRecoginzer]; + + // Fill out the Synergy key map + // (for some reason synergy scan codes are off by 1) + io.KeyMap[ImGuiKey_Tab] = kVK_Tab+1; + io.KeyMap[ImGuiKey_LeftArrow] = kVK_LeftArrow+1; + io.KeyMap[ImGuiKey_RightArrow] = kVK_RightArrow+1; + io.KeyMap[ImGuiKey_UpArrow] = kVK_UpArrow+1; + io.KeyMap[ImGuiKey_DownArrow] = kVK_DownArrow+1; + io.KeyMap[ImGuiKey_Home] = kVK_Home+1; + io.KeyMap[ImGuiKey_End] = kVK_End+1; + io.KeyMap[ImGuiKey_Delete] = kVK_ForwardDelete+1; + io.KeyMap[ImGuiKey_Backspace] = kVK_Delete+1; + io.KeyMap[ImGuiKey_Enter] = kVK_Return+1; + io.KeyMap[ImGuiKey_Escape] = kVK_Escape+1; + io.KeyMap[ImGuiKey_A] = kVK_ANSI_A+1; + io.KeyMap[ImGuiKey_C] = kVK_ANSI_C+1; + io.KeyMap[ImGuiKey_V] = kVK_ANSI_V+1; + io.KeyMap[ImGuiKey_X] = kVK_ANSI_X+1; + io.KeyMap[ImGuiKey_Y] = kVK_ANSI_Y+1; + io.KeyMap[ImGuiKey_Z] = kVK_ANSI_Z+1; + + +} + +- (void)connectServer: (NSString*)serverName +{ + self.serverName = serverName; + g_serverName = serverName; + + // Init synergy + NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey]; + + uSynergyInit( &_synergyCtx ); + _synergyCtx.m_clientName = strdup( [bundleName UTF8String] ); + _synergyCtx.m_clientWidth = self.view.bounds.size.width; + _synergyCtx.m_clientHeight = self.view.bounds.size.height; + + _synergyCtx.m_connectFunc = ImGui_ConnectFunc; + _synergyCtx.m_sendFunc = ImGui_SendFunc; + _synergyCtx.m_receiveFunc = ImGui_RecvFunc; + _synergyCtx.m_sleepFunc = ImGui_SleepFunc; + _synergyCtx.m_traceFunc = ImGui_TraceFunc; + _synergyCtx.m_getTimeFunc = ImGui_GetTimeFunc; + + _synergyCtx.m_traceFunc = ImGui_TraceFunc; + _synergyCtx.m_screenActiveCallback = ImGui_ScreenActiveCallback; + _synergyCtx.m_mouseCallback = ImGui_MouseCallback; + _synergyCtx.m_keyboardCallback = ImGui_KeyboardCallback; + + _synergyCtx.m_cookie = (uSynergyCookie)&_synergyCtx; + + // Create a background thread for synergy + _synergyQueue = dispatch_queue_create( "imgui-usynergy", NULL ); + dispatch_async( _synergyQueue, ^{ + while (1) { + uSynergyUpdate( &_synergyCtx ); + } + }); +} + + +- (void)viewDidPan: (UIPanGestureRecognizer *)recognizer +{ + + if ((recognizer.state == UIGestureRecognizerStateBegan) || + (recognizer.state == UIGestureRecognizerStateChanged)) + { + _mouseDown = YES; + _touchPos = [recognizer locationInView:self.view]; + } + else + { + _mouseDown = NO; + _touchPos = CGPointMake( -1, -1 ); + } +} + +- (void)viewDidTap: (UITapGestureRecognizer*)recognizer +{ + _touchPos = [recognizer locationInView:self.view]; + _mouseTapped = YES; +} + +- (void)render +{ + ImGui::Render(); +} + +- (void)newFrame +{ + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle &style = ImGui::GetStyle(); + + if (!g_FontTexture) + { + ImGui_ImplIOS_CreateDeviceObjects(); + } + + io.DisplaySize = ImVec2( _view.bounds.size.width, _view.bounds.size.height ); + + io.MouseDrawCursor = g_synergyPtrActive; + if (g_synergyPtrActive) + { + style.TouchExtraPadding = ImVec2( 0.0, 0.0 ); + io.MousePos = ImVec2( g_mousePosX, g_mousePosY ); + for (int i=0; i < 3; i++) + { + io.MouseDown[i] = g_MousePressed[i]; + } + + // This is an arbitrary scaling factor that works for me. Not sure what units these + // mousewheel values from synergy are supposed to be in + io.MouseWheel = g_mouseWheelY / 500.0; + } + else + { + // Synergy not active, use touch events + style.TouchExtraPadding = ImVec2( 4.0, 4.0 ); + io.MousePos = ImVec2(_touchPos.x, _touchPos.y ); + if ((_mouseDown) || (_mouseTapped)) + { + io.MouseDown[0] = true; + _mouseTapped = NO; + } + else + { + io.MouseDown[0] = false; + } + } + + ImGui::NewFrame(); +} +@end + +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) +// If text or lines are blurry when integrating ImGui in your engine: +// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) +// NOTE: this is copied pretty much entirely from the opengl3_example, with only minor changes for ES +static void ImGui_ImplIOS_RenderDrawLists (ImDrawList** const cmd_lists, int cmd_lists_count) +{ + if (cmd_lists_count == 0) + { + return; + } + + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled + GLint last_program, last_texture; + glGetIntegerv(GL_CURRENT_PROGRAM, &last_program); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glActiveTexture(GL_TEXTURE0); + + // Setup orthographic projection matrix + const float width = ImGui::GetIO().DisplaySize.x; + const float height = ImGui::GetIO().DisplaySize.y; + const float ortho_projection[4][4] = + { + { 2.0f/width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/-height, 0.0f, 0.0f }, + { 0.0f, 0.0f, -1.0f, 0.0f }, + { -1.0f, 1.0f, 0.0f, 1.0f }, + }; + glUseProgram(g_ShaderHandle); + glUniform1i(g_AttribLocationTex, 0); + glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]); + + // Grow our buffer according to what we need + size_t total_vtx_count = 0; + for (int n = 0; n < cmd_lists_count; n++) + total_vtx_count += cmd_lists[n]->vtx_buffer.size(); + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + size_t needed_vtx_size = total_vtx_count * sizeof(ImDrawVert); + if (g_VboSize < needed_vtx_size) + { + g_VboSize = needed_vtx_size + 5000 * sizeof(ImDrawVert); // Grow buffer + glBufferData(GL_ARRAY_BUFFER, g_VboSize, NULL, GL_STREAM_DRAW); + } + + // Copy and convert all vertices into a single contiguous buffer + unsigned char* buffer_data = (unsigned char*)glMapBufferRange(GL_ARRAY_BUFFER, 0, g_VboSize, GL_MAP_WRITE_BIT); + if (!buffer_data) + return; + for (int n = 0; n < cmd_lists_count; n++) + { + const ImDrawList* cmd_list = cmd_lists[n]; + memcpy(buffer_data, &cmd_list->vtx_buffer[0], cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); + buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert); + } + glUnmapBuffer(GL_ARRAY_BUFFER); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(g_VaoHandle); + + int cmd_offset = 0; + for (int n = 0; n < cmd_lists_count; n++) + { + const ImDrawList* cmd_list = cmd_lists[n]; + int vtx_offset = cmd_offset; + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) + { + if (pcmd->user_callback) + { + pcmd->user_callback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id); + glScissor((int)(pcmd->clip_rect.x * g_displayScale), + (int)((height - pcmd->clip_rect.w) * g_displayScale), + (int)((pcmd->clip_rect.z - pcmd->clip_rect.x) * g_displayScale), + (int)((pcmd->clip_rect.w - pcmd->clip_rect.y) * g_displayScale)); + glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); + } + vtx_offset += pcmd->vtx_count; + } + cmd_offset = vtx_offset; + } + + + // Restore modified state + glBindVertexArray(0); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glUseProgram(last_program); + glDisable(GL_SCISSOR_TEST); + glBindTexture(GL_TEXTURE_2D, last_texture); +} + +void ImGui_ImplIOS_CreateFontsTexture() +{ + ImGuiIO& io = ImGui::GetIO(); + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits for OpenGL3 demo because it is more likely to be compatible with user's existing shader. + + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Cleanup (don't clear the input data if you want to append new fonts later) + io.Fonts->ClearInputData(); + io.Fonts->ClearTexData(); +} + +bool ImGui_ImplIOS_CreateDeviceObjects() +{ + const GLchar *vertex_shader = + "uniform mat4 ProjMtx;\n" + "attribute highp vec2 Position;\n" + "attribute highp vec2 UV;\n" + "attribute highp vec4 Color;\n" + "varying vec2 Frag_UV;\n" + "varying vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " Frag_UV = UV;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" + "}\n"; + + const GLchar* fragment_shader = + "uniform sampler2D Texture;\n" + "varying highp vec2 Frag_UV;\n" + "varying highp vec4 Frag_Color;\n" + "void main()\n" + "{\n" + " gl_FragColor = Frag_Color * texture2D( Texture, Frag_UV.st);\n" + "}\n"; + + g_ShaderHandle = glCreateProgram(); + g_VertHandle = glCreateShader(GL_VERTEX_SHADER); + g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(g_VertHandle, 1, &vertex_shader, 0); + glShaderSource(g_FragHandle, 1, &fragment_shader, 0); + glCompileShader(g_VertHandle); + +#if defined(DEBUG) + GLint logLength; + glGetShaderiv( g_VertHandle, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetShaderInfoLog(g_VertHandle, logLength, &logLength, log); + NSLog(@"VERTEX Shader compile log:\n%s", log); + free(log); + } +#endif + + glCompileShader(g_FragHandle); + +#if defined(DEBUG) + glGetShaderiv( g_FragHandle, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + glGetShaderInfoLog(g_FragHandle, logLength, &logLength, log); + NSLog(@"FRAGMENT Shader compile log:\n%s", log); + free(log); + } +#endif + + + glAttachShader(g_ShaderHandle, g_VertHandle); + glAttachShader(g_ShaderHandle, g_FragHandle); + glLinkProgram(g_ShaderHandle); + + g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position"); + g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV"); + g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color"); + + glGenBuffers(1, &g_VboHandle); + + glGenVertexArrays(1, &g_VaoHandle); + glBindVertexArray(g_VaoHandle); + glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle); + glEnableVertexAttribArray(g_AttribLocationPosition); + glEnableVertexAttribArray(g_AttribLocationUV); + glEnableVertexAttribArray(g_AttribLocationColor); + +#define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) + glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, pos)); + glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, uv)); + glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, col)); +#undef OFFSETOF + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + ImGui_ImplIOS_CreateFontsTexture(); + + return true; +} + diff --git a/examples/ios_example/imguiex/main.m b/examples/ios_example/imguiex/main.m new file mode 100644 index 000000000..faba09928 --- /dev/null +++ b/examples/ios_example/imguiex/main.m @@ -0,0 +1,13 @@ +// +// main.m +// imguiex +// + +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/libs/usynergy/uSynergy.c b/examples/libs/usynergy/uSynergy.c new file mode 100644 index 000000000..a8d01da41 --- /dev/null +++ b/examples/libs/usynergy/uSynergy.c @@ -0,0 +1,636 @@ +/* +uSynergy client -- Implementation for the embedded Synergy client library + version 1.0.0, July 7th, 2012 + +Copyright (c) 2012 Alex Evans + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include "uSynergy.h" +#include +#include + + + +//--------------------------------------------------------------------------------------------------------------------- +// Internal helpers +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief Read 16 bit integer in network byte order and convert to native byte order +**/ +static int16_t sNetToNative16(const unsigned char *value) +{ +#ifdef USYNERGY_LITTLE_ENDIAN + return value[1] | (value[0] << 8); +#else + return value[0] | (value[1] << 8); +#endif +} + + + +/** +@brief Read 32 bit integer in network byte order and convert to native byte order +**/ +static int32_t sNetToNative32(const unsigned char *value) +{ +#ifdef USYNERGY_LITTLE_ENDIAN + return value[3] | (value[2] << 8) | (value[1] << 16) | (value[0] << 24); +#else + return value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24); +#endif +} + + + +/** +@brief Trace text to client +**/ +static void sTrace(uSynergyContext *context, const char* text) +{ + // Don't trace if we don't have a trace function + if (context->m_traceFunc != 0L) + context->m_traceFunc(context->m_cookie, text); +} + + + +/** +@brief Add string to reply packet +**/ +static void sAddString(uSynergyContext *context, const char *string) +{ + size_t len = strlen(string); + memcpy(context->m_replyCur, string, len); + context->m_replyCur += len; +} + + + +/** +@brief Add uint8 to reply packet +**/ +static void sAddUInt8(uSynergyContext *context, uint8_t value) +{ + *context->m_replyCur++ = value; +} + + + +/** +@brief Add uint16 to reply packet +**/ +static void sAddUInt16(uSynergyContext *context, uint16_t value) +{ + uint8_t *reply = context->m_replyCur; + *reply++ = (uint8_t)(value >> 8); + *reply++ = (uint8_t)value; + context->m_replyCur = reply; +} + + + +/** +@brief Add uint32 to reply packet +**/ +static void sAddUInt32(uSynergyContext *context, uint32_t value) +{ + uint8_t *reply = context->m_replyCur; + *reply++ = (uint8_t)(value >> 24); + *reply++ = (uint8_t)(value >> 16); + *reply++ = (uint8_t)(value >> 8); + *reply++ = (uint8_t)value; + context->m_replyCur = reply; +} + + + +/** +@brief Send reply packet +**/ +static uSynergyBool sSendReply(uSynergyContext *context) +{ + // Set header size + uint8_t *reply_buf = context->m_replyBuffer; + uint32_t reply_len = (uint32_t)(context->m_replyCur - reply_buf); /* Total size of reply */ + uint32_t body_len = reply_len - 4; /* Size of body */ + uSynergyBool ret; + reply_buf[0] = (uint8_t)(body_len >> 24); + reply_buf[1] = (uint8_t)(body_len >> 16); + reply_buf[2] = (uint8_t)(body_len >> 8); + reply_buf[3] = (uint8_t)body_len; + + // Send reply + ret = context->m_sendFunc(context->m_cookie, context->m_replyBuffer, reply_len); + + // Reset reply buffer write pointer + context->m_replyCur = context->m_replyBuffer+4; + return ret; +} + + + +/** +@brief Call mouse callback after a mouse event +**/ +static void sSendMouseCallback(uSynergyContext *context) +{ + // Skip if no callback is installed + if (context->m_mouseCallback == 0L) + return; + + // Send callback + context->m_mouseCallback(context->m_cookie, context->m_mouseX, context->m_mouseY, context->m_mouseWheelX, + context->m_mouseWheelY, context->m_mouseButtonLeft, context->m_mouseButtonRight, context->m_mouseButtonMiddle); +} + + + +/** +@brief Send keyboard callback when a key has been pressed or released +**/ +static void sSendKeyboardCallback(uSynergyContext *context, uint16_t key, uint16_t modifiers, uSynergyBool down, uSynergyBool repeat) +{ + // Skip if no callback is installed + if (context->m_keyboardCallback == 0L) + return; + + // Send callback + context->m_keyboardCallback(context->m_cookie, key, modifiers, down, repeat); +} + + + +/** +@brief Send joystick callback +**/ +static void sSendJoystickCallback(uSynergyContext *context, uint8_t joyNum) +{ + int8_t *sticks; + + // Skip if no callback is installed + if (context->m_joystickCallback == 0L) + return; + + // Send callback + sticks = context->m_joystickSticks[joyNum]; + context->m_joystickCallback(context->m_cookie, joyNum, context->m_joystickButtons[joyNum], sticks[0], sticks[1], sticks[2], sticks[3]); +} + + + +/** +@brief Parse a single client message, update state, send callbacks and send replies +**/ +#define USYNERGY_IS_PACKET(pkt_id) memcmp(message+4, pkt_id, 4)==0 +static void sProcessMessage(uSynergyContext *context, const uint8_t *message) +{ + // We have a packet! + if (memcmp(message+4, "Synergy", 7)==0) + { + // Welcome message + // kMsgHello = "Synergy%2i%2i" + // kMsgHelloBack = "Synergy%2i%2i%s" + sAddString(context, "Synergy"); + sAddUInt16(context, USYNERGY_PROTOCOL_MAJOR); + sAddUInt16(context, USYNERGY_PROTOCOL_MINOR); + sAddUInt32(context, (uint32_t)strlen(context->m_clientName)); + sAddString(context, context->m_clientName); + if (!sSendReply(context)) + { + // Send reply failed, let's try to reconnect + sTrace(context, "SendReply failed, trying to reconnect in a second"); + context->m_connected = USYNERGY_FALSE; + context->m_sleepFunc(context->m_cookie, 1000); + } + else + { + // Let's assume we're connected + char buffer[256+1]; + sprintf(buffer, "Connected as client \"%s\"", context->m_clientName); + sTrace(context, buffer); + context->m_hasReceivedHello = USYNERGY_TRUE; + } + return; + } + else if (USYNERGY_IS_PACKET("QINF")) + { + // Screen info. Reply with DINF + // kMsgQInfo = "QINF" + // kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i" + uint16_t x = 0, y = 0, warp = 0; + sAddString(context, "DINF"); + sAddUInt16(context, x); + sAddUInt16(context, y); + sAddUInt16(context, context->m_clientWidth); + sAddUInt16(context, context->m_clientHeight); + sAddUInt16(context, warp); + sAddUInt16(context, 0); // mx? + sAddUInt16(context, 0); // my? + sSendReply(context); + return; + } + else if (USYNERGY_IS_PACKET("CIAK")) + { + // Do nothing? + // kMsgCInfoAck = "CIAK" + return; + } + else if (USYNERGY_IS_PACKET("CROP")) + { + // Do nothing? + // kMsgCResetOptions = "CROP" + return; + } + else if (USYNERGY_IS_PACKET("CINN")) + { + // Screen enter. Reply with CNOP + // kMsgCEnter = "CINN%2i%2i%4i%2i" + + // Obtain the Synergy sequence number + context->m_sequenceNumber = sNetToNative32(message + 12); + context->m_isCaptured = USYNERGY_TRUE; + + // Call callback + if (context->m_screenActiveCallback != 0L) + context->m_screenActiveCallback(context->m_cookie, USYNERGY_TRUE); + } + else if (USYNERGY_IS_PACKET("COUT")) + { + // Screen leave + // kMsgCLeave = "COUT" + context->m_isCaptured = USYNERGY_FALSE; + + // Call callback + if (context->m_screenActiveCallback != 0L) + context->m_screenActiveCallback(context->m_cookie, USYNERGY_FALSE); + } + else if (USYNERGY_IS_PACKET("DMDN")) + { + // Mouse down + // kMsgDMouseDown = "DMDN%1i" + char btn = message[8]-1; + if (btn==2) + context->m_mouseButtonRight = USYNERGY_TRUE; + else if (btn==1) + context->m_mouseButtonMiddle = USYNERGY_TRUE; + else + context->m_mouseButtonLeft = USYNERGY_TRUE; + sSendMouseCallback(context); + } + else if (USYNERGY_IS_PACKET("DMUP")) + { + // Mouse up + // kMsgDMouseUp = "DMUP%1i" + char btn = message[8]-1; + if (btn==2) + context->m_mouseButtonRight = USYNERGY_FALSE; + else if (btn==1) + context->m_mouseButtonMiddle = USYNERGY_FALSE; + else + context->m_mouseButtonLeft = USYNERGY_FALSE; + sSendMouseCallback(context); + } + else if (USYNERGY_IS_PACKET("DMMV")) + { + // Mouse move. Reply with CNOP + // kMsgDMouseMove = "DMMV%2i%2i" + context->m_mouseX = sNetToNative16(message+8); + context->m_mouseY = sNetToNative16(message+10); + sSendMouseCallback(context); + } + else if (USYNERGY_IS_PACKET("DMWM")) + { + // Mouse wheel + // kMsgDMouseWheel = "DMWM%2i%2i" + // kMsgDMouseWheel1_0 = "DMWM%2i" + context->m_mouseWheelX += sNetToNative16(message+8); + context->m_mouseWheelY += sNetToNative16(message+10); + sSendMouseCallback(context); + } + else if (USYNERGY_IS_PACKET("DKDN")) + { + // Key down + // kMsgDKeyDown = "DKDN%2i%2i%2i" + // kMsgDKeyDown1_0 = "DKDN%2i%2i" + //uint16_t id = sNetToNative16(message+8); + uint16_t mod = sNetToNative16(message+10); + uint16_t key = sNetToNative16(message+12); + sSendKeyboardCallback(context, key, mod, USYNERGY_TRUE, USYNERGY_FALSE); + } + else if (USYNERGY_IS_PACKET("DKRP")) + { + // Key repeat + // kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i" + // kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i" + uint16_t mod = sNetToNative16(message+10); +// uint16_t count = sNetToNative16(message+12); + uint16_t key = sNetToNative16(message+14); + sSendKeyboardCallback(context, key, mod, USYNERGY_TRUE, USYNERGY_TRUE); + } + else if (USYNERGY_IS_PACKET("DKUP")) + { + // Key up + // kMsgDKeyUp = "DKUP%2i%2i%2i" + // kMsgDKeyUp1_0 = "DKUP%2i%2i" + //uint16 id=Endian::sNetToNative(sbuf[4]); + uint16_t mod = sNetToNative16(message+10); + uint16_t key = sNetToNative16(message+12); + sSendKeyboardCallback(context, key, mod, USYNERGY_FALSE, USYNERGY_FALSE); + } + else if (USYNERGY_IS_PACKET("DGBT")) + { + // Joystick buttons + // kMsgDGameButtons = "DGBT%1i%2i"; + uint8_t joy_num = message[8]; + if (joy_numm_joystickButtons[joy_num] = (message[9] << 8) | message[10]; + sSendJoystickCallback(context, joy_num); + } + } + else if (USYNERGY_IS_PACKET("DGST")) + { + // Joystick sticks + // kMsgDGameSticks = "DGST%1i%1i%1i%1i%1i"; + uint8_t joy_num = message[8]; + if (joy_numm_joystickSticks[joy_num], message+9, 4); + sSendJoystickCallback(context, joy_num); + } + } + else if (USYNERGY_IS_PACKET("DSOP")) + { + // Set options + // kMsgDSetOptions = "DSOP%4I" + } + else if (USYNERGY_IS_PACKET("CALV")) + { + // Keepalive, reply with CALV and then CNOP + // kMsgCKeepAlive = "CALV" + sAddString(context, "CALV"); + sSendReply(context); + // now reply with CNOP + } + else if (USYNERGY_IS_PACKET("DCLP")) + { + // Clipboard message + // kMsgDClipboard = "DCLP%1i%4i%s" + // + // The clipboard message contains: + // 1 uint32: The size of the message + // 4 chars: The identifier ("DCLP") + // 1 uint8: The clipboard index + // 1 uint32: The sequence number. It's zero, because this message is always coming from the server? + // 1 uint32: The total size of the remaining 'string' (as per the Synergy %s string format (which is 1 uint32 for size followed by a char buffer (not necessarily null terminated)). + // 1 uint32: The number of formats present in the message + // And then 'number of formats' times the following: + // 1 uint32: The format of the clipboard data + // 1 uint32: The size n of the clipboard data + // n uint8: The clipboard data + const uint8_t * parse_msg = message+17; + uint32_t num_formats = sNetToNative32(parse_msg); + parse_msg += 4; + for (; num_formats; num_formats--) + { + // Parse clipboard format header + uint32_t format = sNetToNative32(parse_msg); + uint32_t size = sNetToNative32(parse_msg+4); + parse_msg += 8; + + // Call callback + if (context->m_clipboardCallback) + context->m_clipboardCallback(context->m_cookie, format, parse_msg, size); + + parse_msg += size; + } + } + else + { + // Unknown packet, could be any of these + // kMsgCNoop = "CNOP" + // kMsgCClose = "CBYE" + // kMsgCClipboard = "CCLP%1i%4i" + // kMsgCScreenSaver = "CSEC%1i" + // kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i" + // kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i" + // kMsgDMouseRelMove = "DMRM%2i%2i" + // kMsgEIncompatible = "EICV%2i%2i" + // kMsgEBusy = "EBSY" + // kMsgEUnknown = "EUNK" + // kMsgEBad = "EBAD" + char buffer[64]; + sprintf(buffer, "Unknown packet '%c%c%c%c'", message[4], message[5], message[6], message[7]); + sTrace(context, buffer); + return; + } + + // Reply with CNOP maybe? + sAddString(context, "CNOP"); + sSendReply(context); +} +#undef USYNERGY_IS_PACKET + + + +/** +@brief Mark context as being disconnected +**/ +static void sSetDisconnected(uSynergyContext *context) +{ + context->m_connected = USYNERGY_FALSE; + context->m_hasReceivedHello = USYNERGY_FALSE; + context->m_isCaptured = USYNERGY_FALSE; + context->m_replyCur = context->m_replyBuffer + 4; + context->m_sequenceNumber = 0; +} + + + +/** +@brief Update a connected context +**/ +static void sUpdateContext(uSynergyContext *context) +{ + /* Receive data (blocking) */ + int receive_size = USYNERGY_RECEIVE_BUFFER_SIZE - context->m_receiveOfs; + int num_received = 0; + int packlen = 0; + if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer + context->m_receiveOfs, receive_size, &num_received) == USYNERGY_FALSE) + { + /* Receive failed, let's try to reconnect */ + char buffer[128]; + sprintf(buffer, "Receive failed (%d bytes asked, %d bytes received), trying to reconnect in a second", receive_size, num_received); + sTrace(context, buffer); + sSetDisconnected(context); + context->m_sleepFunc(context->m_cookie, 1000); + return; + } + context->m_receiveOfs += num_received; + + /* If we didn't receive any data then we're probably still polling to get connected and + therefore not getting any data back. To avoid overloading the system with a Synergy + thread that would hammer on polling, we let it rest for a bit if there's no data. */ + if (num_received == 0) + context->m_sleepFunc(context->m_cookie, 500); + + /* Check for timeouts */ + if (context->m_hasReceivedHello) + { + uint32_t cur_time = context->m_getTimeFunc(); + if (num_received == 0) + { + /* Timeout after 2 secs of inactivity (we received no CALV) */ + if ((cur_time - context->m_lastMessageTime) > USYNERGY_IDLE_TIMEOUT) + sSetDisconnected(context); + } + else + context->m_lastMessageTime = cur_time; + } + + /* Eat packets */ + for (;;) + { + /* Grab packet length and bail out if the packet goes beyond the end of the buffer */ + packlen = sNetToNative32(context->m_receiveBuffer); + if (packlen+4 > context->m_receiveOfs) + break; + + /* Process message */ + sProcessMessage(context, context->m_receiveBuffer); + + /* Move packet to front of buffer */ + memmove(context->m_receiveBuffer, context->m_receiveBuffer+packlen+4, context->m_receiveOfs-packlen-4); + context->m_receiveOfs -= packlen+4; + } + + /* Throw away over-sized packets */ + if (packlen > USYNERGY_RECEIVE_BUFFER_SIZE) + { + /* Oversized packet, ditch tail end */ + char buffer[128]; + sprintf(buffer, "Oversized packet: '%c%c%c%c' (length %d)", context->m_receiveBuffer[4], context->m_receiveBuffer[5], context->m_receiveBuffer[6], context->m_receiveBuffer[7], packlen); + sTrace(context, buffer); + num_received = context->m_receiveOfs-4; // 4 bytes for the size field + while (num_received != packlen) + { + int buffer_left = packlen - num_received; + int to_receive = buffer_left < USYNERGY_RECEIVE_BUFFER_SIZE ? buffer_left : USYNERGY_RECEIVE_BUFFER_SIZE; + int ditch_received = 0; + if (context->m_receiveFunc(context->m_cookie, context->m_receiveBuffer, to_receive, &ditch_received) == USYNERGY_FALSE) + { + /* Receive failed, let's try to reconnect */ + sTrace(context, "Receive failed, trying to reconnect in a second"); + sSetDisconnected(context); + context->m_sleepFunc(context->m_cookie, 1000); + break; + } + else + { + num_received += ditch_received; + } + } + context->m_receiveOfs = 0; + } +} + + +//--------------------------------------------------------------------------------------------------------------------- +// Public interface +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief Initialize uSynergy context +**/ +void uSynergyInit(uSynergyContext *context) +{ + /* Zero memory */ + memset(context, 0, sizeof(uSynergyContext)); + + /* Initialize to default state */ + sSetDisconnected(context); +} + + +/** +@brief Update uSynergy +**/ +void uSynergyUpdate(uSynergyContext *context) +{ + if (context->m_connected) + { + /* Update context, receive data, call callbacks */ + sUpdateContext(context); + } + else + { + /* Try to connect */ + if (context->m_connectFunc(context->m_cookie)) + context->m_connected = USYNERGY_TRUE; + } +} + + + +/** +@brief Send clipboard data +**/ +void uSynergySendClipboard(uSynergyContext *context, const char *text) +{ + // Calculate maximum size that will fit in a reply packet + uint32_t overhead_size = 4 + /* Message size */ + 4 + /* Message ID */ + 1 + /* Clipboard index */ + 4 + /* Sequence number */ + 4 + /* Rest of message size (because it's a Synergy string from here on) */ + 4 + /* Number of clipboard formats */ + 4 + /* Clipboard format */ + 4; /* Clipboard data length */ + uint32_t max_length = USYNERGY_REPLY_BUFFER_SIZE - overhead_size; + + // Clip text to max length + uint32_t text_length = (uint32_t)strlen(text); + if (text_length > max_length) + { + char buffer[128]; + sprintf(buffer, "Clipboard buffer too small, clipboard truncated at %d characters", max_length); + sTrace(context, buffer); + text_length = max_length; + } + + // Assemble packet + sAddString(context, "DCLP"); + sAddUInt8(context, 0); /* Clipboard index */ + sAddUInt32(context, context->m_sequenceNumber); + sAddUInt32(context, 4+4+4+text_length); /* Rest of message size: numFormats, format, length, data */ + sAddUInt32(context, 1); /* Number of formats (only text for now) */ + sAddUInt32(context, USYNERGY_CLIPBOARD_FORMAT_TEXT); + sAddUInt32(context, text_length); + sAddString(context, text); + sSendReply(context); +} diff --git a/examples/libs/usynergy/uSynergy.h b/examples/libs/usynergy/uSynergy.h new file mode 100644 index 000000000..cedc38783 --- /dev/null +++ b/examples/libs/usynergy/uSynergy.h @@ -0,0 +1,420 @@ +/* +uSynergy client -- Interface for the embedded Synergy client library + version 1.0.0, July 7th, 2012 + +Copyright (C) 2012 Synergy Si Ltd. +Copyright (c) 2012 Alex Evans + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +//--------------------------------------------------------------------------------------------------------------------- +// Configuration +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief Determine endianness +**/ +#if defined(USYNERGY_LITTLE_ENDIAN) && defined(USYNERGY_BIG_ENDIAN) + /* Ambiguous: both endians specified */ + #error "Can't define both USYNERGY_LITTLE_ENDIAN and USYNERGY_BIG_ENDIAN" +#elif !defined(USYNERGY_LITTLE_ENDIAN) && !defined(USYNERGY_BIG_ENDIAN) + /* Attempt to auto detect */ + #if defined(__LITTLE_ENDIAN__) || defined(LITTLE_ENDIAN) || (_BYTE_ORDER == _LITTLE_ENDIAN) + #define USYNERGY_LITTLE_ENDIAN + #elif defined(__BIG_ENDIAN__) || defined(BIG_ENDIAN) || (_BYTE_ORDER == _BIG_ENDIAN) + #define USYNERGY_BIG_ENDIAN + #else + #error "Can't detect endian-nes, please defined either USYNERGY_LITTLE_ENDIAN or USYNERGY_BIG_ENDIAN"; + #endif +#else + /* User-specified endian-nes, nothing to do for us */ +#endif + + + +//--------------------------------------------------------------------------------------------------------------------- +// Types and Constants +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief Boolean type +**/ +typedef int uSynergyBool; +#define USYNERGY_FALSE 0 /* False value */ +#define USYNERGY_TRUE 1 /* True value */ + + +/** +@brief User context type + +The uSynergyCookie type is an opaque type that is used by uSynergy to communicate to the client. It is passed along to +callback functions as context. +**/ +typedef struct { int ignored; } * uSynergyCookie; + + + +/** +@brief Clipboard types +**/ +enum uSynergyClipboardFormat +{ + USYNERGY_CLIPBOARD_FORMAT_TEXT = 0, /* Text format, UTF-8, newline is LF */ + USYNERGY_CLIPBOARD_FORMAT_BITMAP = 1, /* Bitmap format, BMP 24/32bpp, BI_RGB */ + USYNERGY_CLIPBOARD_FORMAT_HTML = 2, /* HTML format, HTML fragment, UTF-8, newline is LF */ +}; + + + +/** +@brief Constants and limits +**/ +#define USYNERGY_NUM_JOYSTICKS 4 /* Maximum number of supported joysticks */ + +#define USYNERGY_PROTOCOL_MAJOR 1 /* Major protocol version */ +#define USYNERGY_PROTOCOL_MINOR 4 /* Minor protocol version */ + +#define USYNERGY_IDLE_TIMEOUT 2000 /* Timeout in milliseconds before reconnecting */ + +#define USYNERGY_TRACE_BUFFER_SIZE 1024 /* Maximum length of traced message */ +#define USYNERGY_REPLY_BUFFER_SIZE 1024 /* Maximum size of a reply packet */ +#define USYNERGY_RECEIVE_BUFFER_SIZE 4096 /* Maximum size of an incoming packet */ + + + +/** +@brief Keyboard constants +**/ +#define USYNERGY_MODIFIER_SHIFT 0x0001 /* Shift key modifier */ +#define USYNERGY_MODIFIER_CTRL 0x0002 /* Ctrl key modifier */ +#define USYNERGY_MODIFIER_ALT 0x0004 /* Alt key modifier */ +#define USYNERGY_MODIFIER_META 0x0008 /* Meta key modifier */ +#define USYNERGY_MODIFIER_WIN 0x0010 /* Windows key modifier */ +#define USYNERGY_MODIFIER_ALT_GR 0x0020 /* AltGr key modifier */ +#define USYNERGY_MODIFIER_LEVEL5LOCK 0x0040 /* Level5Lock key modifier */ +#define USYNERGY_MODIFIER_CAPSLOCK 0x1000 /* CapsLock key modifier */ +#define USYNERGY_MODIFIER_NUMLOCK 0x2000 /* NumLock key modifier */ +#define USYNERGY_MODIFIER_SCROLLOCK 0x4000 /* ScrollLock key modifier */ + + + + +//--------------------------------------------------------------------------------------------------------------------- +// Functions and Callbacks +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief Connect function + +This function is called when uSynergy needs to connect to the host. It doesn't imply a network implementation or +destination address, that must all be handled on the user side. The function should return USYNERGY_TRUE if a +connection was established or USYNERGY_FALSE if it could not connect. + +When network errors occur (e.g. uSynergySend or uSynergyReceive fail) then the connect call will be called again +so the implementation of the function must close any old connections and clean up resources before retrying. + +@param cookie Cookie supplied in the Synergy context +**/ +typedef uSynergyBool (*uSynergyConnectFunc)(uSynergyCookie cookie); + + + +/** +@brief Send function + +This function is called when uSynergy needs to send something over the default connection. It should return +USYNERGY_TRUE if sending succeeded and USYNERGY_FALSE otherwise. This function should block until the send +operation is completed. + +@param cookie Cookie supplied in the Synergy context +@param buffer Address of buffer to send +@param length Length of buffer to send +**/ +typedef uSynergyBool (*uSynergySendFunc)(uSynergyCookie cookie, const uint8_t *buffer, int length); + + + +/** +@brief Receive function + +This function is called when uSynergy needs to receive data from the default connection. It should return +USYNERGY_TRUE if receiving data succeeded and USYNERGY_FALSE otherwise. This function should block until data +has been received and wait for data to become available. If @a outLength is set to 0 upon completion it is +assumed that the connection is alive, but still in a connecting state and needs time to settle. + +@param cookie Cookie supplied in the Synergy context +@param buffer Address of buffer to receive data into +@param maxLength Maximum amount of bytes to write into the receive buffer +@param outLength Address of integer that receives the actual amount of bytes written into @a buffer +**/ +typedef uSynergyBool (*uSynergyReceiveFunc)(uSynergyCookie cookie, uint8_t *buffer, int maxLength, int* outLength); + + + +/** +@brief Thread sleep function + +This function is called when uSynergy wants to suspend operation for a while before retrying an operation. It +is mostly used when a socket times out or disconnect occurs to prevent uSynergy from continuously hammering a +network connection in case the network is down. + +@param cookie Cookie supplied in the Synergy context +@param timeMs Time to sleep the current thread (in milliseconds) +**/ +typedef void (*uSynergySleepFunc)(uSynergyCookie cookie, int timeMs); + + + +/** +@brief Get time function + +This function is called when uSynergy needs to know the current time. This is used to determine when timeouts +have occured. The time base should be a cyclic millisecond time value. + +@returns Time value in milliseconds +**/ +typedef uint32_t (*uSynergyGetTimeFunc)(); + + + +/** +@brief Trace function + +This function is called when uSynergy wants to trace something. It is optional to show these messages, but they +are often useful when debugging. uSynergy only traces major events like connecting and disconnecting. Usually +only a single trace is shown when the connection is established and no more trace are called. + +@param cookie Cookie supplied in the Synergy context +@param text Text to be traced +**/ +typedef void (*uSynergyTraceFunc)(uSynergyCookie cookie, const char *text); + + + +/** +@brief Screen active callback + +This callback is called when Synergy makes the screen active or inactive. This +callback is usually sent when the mouse enters or leaves the screen. + +@param cookie Cookie supplied in the Synergy context +@param active Activation flag, 1 if the screen has become active, 0 if the screen has become inactive +**/ +typedef void (*uSynergyScreenActiveCallback)(uSynergyCookie cookie, uSynergyBool active); + + + +/** +@brief Mouse callback + +This callback is called when a mouse events happens. The mouse X and Y position, +wheel and button state is communicated in the message. It's up to the user to +interpret if this is a mouse up, down, double-click or other message. + +@param cookie Cookie supplied in the Synergy context +@param x Mouse X position +@param y Mouse Y position +@param wheelX Mouse wheel X position +@param wheelY Mouse wheel Y position +@param buttonLeft Left button pressed status, 0 for released, 1 for pressed +@param buttonMiddle Middle button pressed status, 0 for released, 1 for pressed +@param buttonRight Right button pressed status, 0 for released, 1 for pressed +**/ +typedef void (*uSynergyMouseCallback)(uSynergyCookie cookie, uint16_t x, uint16_t y, int16_t wheelX, int16_t wheelY, uSynergyBool buttonLeft, uSynergyBool buttonRight, uSynergyBool buttonMiddle); + + + +/** +@brief Key event callback + +This callback is called when a key is pressed or released. + +@param cookie Cookie supplied in the Synergy context +@param key Key code of key that was pressed or released +@param modifiers Status of modifier keys (alt, shift, etc.) +@param down Down or up status, 1 is key is pressed down, 0 if key is released (up) +@param repeat Repeat flag, 1 if the key is down because the key is repeating, 0 if the key is initially pressed by the user +**/ +typedef void (*uSynergyKeyboardCallback)(uSynergyCookie cookie, uint16_t key, uint16_t modifiers, uSynergyBool down, uSynergyBool repeat); + + + +/** +@brief Joystick event callback + +This callback is called when a joystick stick or button changes. It is possible that multiple callbacks are +fired when different sticks or buttons change as these are individual messages in the packet stream. Each +callback will contain all the valid state for the different axes and buttons. The last callback received will +represent the most current joystick state. + +@param cookie Cookie supplied in the Synergy context +@param joyNum Joystick number, always in the range [0 ... USYNERGY_NUM_JOYSTICKS> +@param buttons Button pressed mask +@param leftStickX Left stick X position, in range [-127 ... 127] +@param leftStickY Left stick Y position, in range [-127 ... 127] +@param rightStickX Right stick X position, in range [-127 ... 127] +@param rightStickY Right stick Y position, in range [-127 ... 127] +**/ +typedef void (*uSynergyJoystickCallback)(uSynergyCookie cookie, uint8_t joyNum, uint16_t buttons, int8_t leftStickX, int8_t leftStickY, int8_t rightStickX, int8_t rightStickY); + + + +/** +@brief Clipboard event callback + +This callback is called when something is placed on the clipboard. Multiple callbacks may be fired for +multiple clipboard formats if they are supported. The data provided is read-only and may not be modified +by the application. + +@param cookie Cookie supplied in the Synergy context +@param format Clipboard format +@param data Memory area containing the clipboard raw data +@param size Size of clipboard data +**/ +typedef void (*uSynergyClipboardCallback)(uSynergyCookie cookie, enum uSynergyClipboardFormat format, const uint8_t *data, uint32_t size); + + + +//--------------------------------------------------------------------------------------------------------------------- +// Context +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief uSynergy context +**/ +typedef struct +{ + /* Mandatory configuration data, filled in by client */ + uSynergyConnectFunc m_connectFunc; /* Connect function */ + uSynergySendFunc m_sendFunc; /* Send data function */ + uSynergyReceiveFunc m_receiveFunc; /* Receive data function */ + uSynergySleepFunc m_sleepFunc; /* Thread sleep function */ + uSynergyGetTimeFunc m_getTimeFunc; /* Get current time function */ + const char* m_clientName; /* Name of Synergy Screen / Client */ + uint16_t m_clientWidth; /* Width of screen */ + uint16_t m_clientHeight; /* Height of screen */ + + /* Optional configuration data, filled in by client */ + uSynergyCookie m_cookie; /* Cookie pointer passed to callback functions (can be NULL) */ + uSynergyTraceFunc m_traceFunc; /* Function for tracing status (can be NULL) */ + uSynergyScreenActiveCallback m_screenActiveCallback; /* Callback for entering and leaving screen */ + uSynergyMouseCallback m_mouseCallback; /* Callback for mouse events */ + uSynergyKeyboardCallback m_keyboardCallback; /* Callback for keyboard events */ + uSynergyJoystickCallback m_joystickCallback; /* Callback for joystick events */ + uSynergyClipboardCallback m_clipboardCallback; /* Callback for clipboard events */ + + /* State data, used internall by client, initialized by uSynergyInit() */ + uSynergyBool m_connected; /* Is our socket connected? */ + uSynergyBool m_hasReceivedHello; /* Have we received a 'Hello' from the server? */ + uSynergyBool m_isCaptured; /* Is Synergy active (i.e. this client is receiving input messages?) */ + uint32_t m_lastMessageTime; /* Time at which last message was received */ + uint32_t m_sequenceNumber; /* Packet sequence number */ + uint8_t m_receiveBuffer[USYNERGY_RECEIVE_BUFFER_SIZE]; /* Receive buffer */ + int m_receiveOfs; /* Receive buffer offset */ + uint8_t m_replyBuffer[USYNERGY_REPLY_BUFFER_SIZE]; /* Reply buffer */ + uint8_t* m_replyCur; /* Write offset into reply buffer */ + uint16_t m_mouseX; /* Mouse X position */ + uint16_t m_mouseY; /* Mouse Y position */ + int16_t m_mouseWheelX; /* Mouse wheel X position */ + int16_t m_mouseWheelY; /* Mouse wheel Y position */ + uSynergyBool m_mouseButtonLeft; /* Mouse left button */ + uSynergyBool m_mouseButtonRight; /* Mouse right button */ + uSynergyBool m_mouseButtonMiddle; /* Mouse middle button */ + int8_t m_joystickSticks[USYNERGY_NUM_JOYSTICKS][4]; /* Joystick stick position in 2 axes for 2 sticks */ + uint16_t m_joystickButtons[USYNERGY_NUM_JOYSTICKS]; /* Joystick button state */ +} uSynergyContext; + + + +//--------------------------------------------------------------------------------------------------------------------- +// Interface +//--------------------------------------------------------------------------------------------------------------------- + + + +/** +@brief Initialize uSynergy context + +This function initializes @a context for use. Call this function directly after +creating the context, before filling in any configuration data in it. Not calling +this function will cause undefined behavior. + +@param context Context to be initialized +**/ +extern void uSynergyInit(uSynergyContext *context); + + + +/** +@brief Update uSynergy + +This function updates uSynergy and does the bulk of the work. It does connection management, +receiving data, reconnecting after errors or timeouts and so on. It assumes that networking +operations are blocking and it can suspend the current thread if it needs to wait. It is +best practice to call uSynergyUpdate from a background thread so it is responsive. + +Because uSynergy relies mostly on blocking calls it will mostly stay in thread sleep state +waiting for system mutexes and won't eat much memory. + +uSynergyUpdate doesn't do any memory allocations or have any side effects beyond those of +the callbacks it calls. + +@param context Context to be updated +**/ +extern void uSynergyUpdate(uSynergyContext *context); + + + +/** +@brief Send clipboard data + +This function sets new clipboard data and sends it to the server. Use this function if +your client cuts or copies data onto the clipboard that it needs to share with the +server. + +Currently there is only support for plaintext, but HTML and image data could be +supported with some effort. + +@param context Context to send clipboard data to +@param text Text to set to the clipboard +**/ +extern void uSynergySendClipboard(uSynergyContext *context, const char *text); + + + +#ifdef __cplusplus +}; +#endif From fb27360ea84bdeaab996120a1da047e72af0f3a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2015 21:35:09 -0600 Subject: [PATCH 2/3] iOS example: shallow tweaks and fixes (untested) --- examples/README.txt | 5 ++- examples/ios_example/README.md | 8 ++-- .../ios_example/imguiex/GameViewController.h | 3 +- examples/ios_example/imguiex/debug_hud.cpp | 38 ++++++++----------- examples/ios_example/imguiex/debug_hud.h | 9 ++--- examples/ios_example/imguiex/imgui_impl_ios.h | 6 +-- .../ios_example/imguiex/imgui_impl_ios.mm | 18 ++------- 7 files changed, 32 insertions(+), 55 deletions(-) diff --git a/examples/README.txt b/examples/README.txt index c675a1d73..0a5ad0e05 100644 --- a/examples/README.txt +++ b/examples/README.txt @@ -11,7 +11,7 @@ opengl_example/ Prefer following this example since it is the shortest one! opengl3_example/ - OpenGL exemple, using GLFW/GL3W + programmable pipeline. + OpenGL example, using GLFW/GL3W + programmable pipeline. This uses more modern calls and custom shaders. I don't think there is an advantage using this over the simpler example, but it is provided for reference. @@ -22,3 +22,6 @@ directx11_example/ DirectX11 example, Windows only. This is quite long and tedious, because: DirectX11. +ios_example/ + iOS example. + Using Synergy to access keyboard/mouse data from server computer. Synergy keyboard integration is rather hacky. diff --git a/examples/ios_example/README.md b/examples/ios_example/README.md index fc179f0d0..5b51b905b 100644 --- a/examples/ios_example/README.md +++ b/examples/ios_example/README.md @@ -3,9 +3,9 @@ ---- ## Introduction -This example is the default XCode "OpenGL" example code, modified to support IMGUI and [Synergy](http://synergy-project.org/). +This example is the default XCode "OpenGL" example code, modified to support ImGui and [Synergy](http://synergy-project.org/). -Synergy (remote keyboard/mouse) is not required, but it's pretty hard to use IMGUI without it. Synergy includes a "uSynergy" library that allows embedding a synergy client, this is what is used here. IMGUI supports "Touch Padding", and this is enabled when Synergy is not active. +Synergy (remote keyboard/mouse) is not required, but it's pretty hard to use ImGui without it. Synergy includes a "uSynergy" library that allows embedding a synergy client, this is what is used here. ImGui supports "TouchPadding", and this is enabled when Synergy is not active. ## How to Use ---- @@ -28,8 +28,8 @@ Things that would be nice but I didn't get around to doing: ---- ## C++ on iOS -IMGUI is a c++ library. If you want to include it directly, rename your Obj-C file to have the ".mm" extension. +ImGui is a c++ library. If you want to include it directly, rename your Obj-C file to have the ".mm" extension. Alternatively, you can wrap your debug code in a C interface, this is what I am demonstrating here with the "debug_hud.h" interface. Either approach works, use whatever you prefer. -In my case, most of my game code is already in C++ so it's not really an issue and I can use IMGUI directly. +In my case, most of my game code is already in C++ so it's not really an issue and I can use ImGui directly. diff --git a/examples/ios_example/imguiex/GameViewController.h b/examples/ios_example/imguiex/GameViewController.h index 7c7eeeae5..3323cfd55 100644 --- a/examples/ios_example/imguiex/GameViewController.h +++ b/examples/ios_example/imguiex/GameViewController.h @@ -2,8 +2,7 @@ // GameViewController.h // imguiex -// This is the OpenGL Example template from XCode, modified to -// support IMGUI +// This is the OpenGL Example template from XCode, modified to support ImGui #import #import diff --git a/examples/ios_example/imguiex/debug_hud.cpp b/examples/ios_example/imguiex/debug_hud.cpp index 5d364d7b4..c75938a39 100644 --- a/examples/ios_example/imguiex/debug_hud.cpp +++ b/examples/ios_example/imguiex/debug_hud.cpp @@ -9,20 +9,19 @@ void DebugHUD_InitDefaults( DebugHUD *hud ) { - hud->show_test_window = 1; - hud->show_example_window = 1; - hud->rotation_speed = 15.0; + hud->show_test_window = true; + hud->show_example_window = true; + hud->rotation_speed = 15.0f; - hud->cubeColor1[0] = 0.4; - hud->cubeColor1[1] = 0.4; - hud->cubeColor1[2] = 1.0; - hud->cubeColor1[3] = 1.0; - - hud->cubeColor2[0] = 1.0; - hud->cubeColor2[1] = 0.4; - hud->cubeColor2[2] = 0.4; - hud->cubeColor2[3] = 1.0; + hud->cubeColor1[0] = 0.4f; + hud->cubeColor1[1] = 0.4f; + hud->cubeColor1[2] = 1.0f; + hud->cubeColor1[3] = 1.0f; + hud->cubeColor2[0] = 1.0f; + hud->cubeColor2[1] = 0.4f; + hud->cubeColor2[2] = 0.4f; + hud->cubeColor2[3] = 1.0f; } void DebugHUD_DoInterface( DebugHUD *hud ) @@ -30,24 +29,17 @@ void DebugHUD_DoInterface( DebugHUD *hud ) if (hud->show_test_window) { ImGui::SetNextWindowPos( ImVec2( 400, 20 ), ImGuiSetCond_FirstUseEver ); - bool show_test_window = hud->show_test_window; - ImGui::ShowTestWindow( &show_test_window ); - hud->show_test_window = show_test_window; + ImGui::ShowTestWindow( &hud->show_test_window ); } if (hud->show_example_window) { - bool show_window = hud->show_example_window; ImGui::SetNextWindowPos( ImVec2( 20, 20 ), ImGuiSetCond_FirstUseEver ); ImGui::SetNextWindowSize( ImVec2( 350, 200 ), ImGuiSetCond_FirstUseEver ); - ImGui::Begin("Another Window", &show_window); - hud->show_example_window = show_window; - + ImGui::Begin("Another Window", &hud->show_example_window); ImGui::ColorEdit3("Cube 1 Color", hud->cubeColor1); ImGui::ColorEdit3("Cube 2 Color", hud->cubeColor2); - ImGui::SliderFloat("Rotation Speed", &(hud->rotation_speed), 0.0f, 200.0f); - + ImGui::SliderFloat("Rotation Speed", &hud->rotation_speed, 0.0f, 200.0f); ImGui::End(); } - -} \ No newline at end of file +} diff --git a/examples/ios_example/imguiex/debug_hud.h b/examples/ios_example/imguiex/debug_hud.h index 6df9ff0cb..17122f5e3 100644 --- a/examples/ios_example/imguiex/debug_hud.h +++ b/examples/ios_example/imguiex/debug_hud.h @@ -2,13 +2,12 @@ // debug_hud.h // imguiex -#ifndef __imguiex__debug_hud__ -#define __imguiex__debug_hud__ +#pragma once typedef struct DebugHUD { - int show_test_window; - int show_example_window; + bool show_test_window; + bool show_example_window; float rotation_speed; float cubeColor1[4]; float cubeColor2[4]; @@ -24,5 +23,3 @@ void DebugHUD_DoInterface( DebugHUD *hud ); #if __cplusplus } #endif - -#endif /* defined(__imguiex__debug_hud__) */ diff --git a/examples/ios_example/imguiex/imgui_impl_ios.h b/examples/ios_example/imguiex/imgui_impl_ios.h index b882c1c10..132e589d3 100644 --- a/examples/ios_example/imguiex/imgui_impl_ios.h +++ b/examples/ios_example/imguiex/imgui_impl_ios.h @@ -5,8 +5,7 @@ // Joel Davis (joeld42@gmail.com) // -#ifndef __imguiex__imgui_impl_ios__ -#define __imguiex__imgui_impl_ios__ +#pragma once #include #include @@ -21,6 +20,3 @@ - (void)newFrame; @end - - -#endif /* defined(__imguiex__imgui_impl_ios__) */ diff --git a/examples/ios_example/imguiex/imgui_impl_ios.mm b/examples/ios_example/imguiex/imgui_impl_ios.mm index c7cf41dfb..9b6513ac6 100644 --- a/examples/ios_example/imguiex/imgui_impl_ios.mm +++ b/examples/ios_example/imguiex/imgui_impl_ios.mm @@ -16,6 +16,7 @@ #include "uSynergy.h" // From Carbon HIToolbox/Events.h +// FIXME: Keyboard mapping is hacked in because Synergy doesn't give us character but only keycode which aren't really portable if you consider keyboard locale. See https://github.com/ocornut/imgui/pull/247 enum { kVK_ANSI_A = 0x00, kVK_ANSI_S = 0x01, @@ -267,15 +268,10 @@ void ImGui_KeyboardCallback(uSynergyCookie cookie, uint16_t key, // Add this as keyboard input - if ((down) && (key) && (scanCode<256) && !(modifiers & USYNERGY_MODIFIER_CTRL)) { - int charForKeycode = 0; - if (modifiers & USYNERGY_MODIFIER_SHIFT ) { - charForKeycode = g_keycodeCharShifted[ scanCode ]; - } else { - charForKeycode = g_keycodeCharUnshifted[ scanCode ]; - } - + if ((down) && (key) && (scanCode<256) && !(modifiers & USYNERGY_MODIFIER_CTRL)) + { // If this key maps to a character input, apply it + int charForKeycode = (modifiers & USYNERGY_MODIFIER_SHIFT) ? g_keycodeCharShifted[scanCode] : g_keycodeCharUnshifted[scanCode]; io.AddInputCharacter((unsigned short)charForKeycode); } @@ -499,8 +495,6 @@ void ImGui_ClipboardCallback(uSynergyCookie cookie, enum uSynergyClipboardFormat io.KeyMap[ImGuiKey_X] = kVK_ANSI_X+1; io.KeyMap[ImGuiKey_Y] = kVK_ANSI_Y+1; io.KeyMap[ImGuiKey_Z] = kVK_ANSI_Z+1; - - } - (void)connectServer: (NSString*)serverName @@ -620,9 +614,7 @@ void ImGui_ClipboardCallback(uSynergyCookie cookie, enum uSynergyClipboardFormat static void ImGui_ImplIOS_RenderDrawLists (ImDrawList** const cmd_lists, int cmd_lists_count) { if (cmd_lists_count == 0) - { return; - } // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled GLint last_program, last_texture; @@ -703,7 +695,6 @@ static void ImGui_ImplIOS_RenderDrawLists (ImDrawList** const cmd_lists, int cmd cmd_offset = vtx_offset; } - // Restore modified state glBindVertexArray(0); glEnable(GL_CULL_FACE); @@ -822,4 +813,3 @@ bool ImGui_ImplIOS_CreateDeviceObjects() return true; } - From 7d144eb899aa9feb83d91bdcdb2b2f8972713bc1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jul 2015 21:37:50 -0600 Subject: [PATCH 3/3] iOS Example: Optimise PNG files size --- .../icon_imgui_60@2x~iphone.png | Bin 4491 -> 3974 bytes .../icon_imgui_60@3x~iphone.png | Bin 7022 -> 5953 bytes .../icon_imgui_76@2x~ipad.png | Bin 5727 -> 4959 bytes .../AppIcon.appiconset/icon_imgui_76~ipad.png | Bin 2838 -> 2576 bytes .../ios_example/imguiex/imgui_ex_icon.png | Bin 16394 -> 12653 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_60@2x~iphone.png b/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_60@2x~iphone.png index 0b154f349e126ce471a1f78a6ce98899084932e0..d728bc3fe627b885d0c9df2ce95ddd9bc89ee419 100644 GIT binary patch literal 3974 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 literal 4491 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 aW)oPOXB1N~w0~dYfLBm;#aac6u>S!7Vo}-v literal 7022 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 literal 5727 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 diff --git a/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_76~ipad.png b/examples/ios_example/imguiex/Images.xcassets/AppIcon.appiconset/icon_imgui_76~ipad.png index 8813551e6bf63329171151e62cc080f72d82c87d..ae88e04d9c50882bb5636c6514ef03ba7214e3d4 100644 GIT binary patch delta 2568 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