diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..56a33f06425feaaf69a41c710b30f9f990a3dbb6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,74 @@
+# Visual Studio 2015 user specific files
+.vs/
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+*.ipa
+
+# These project files can be generated by the engine
+*.xcodeproj
+*.xcworkspace
+*.sln
+*.suo
+*.opensdf
+*.sdf
+*.VC.db
+*.VC.opendb
+
+# Precompiled Assets
+SourceArt/**/*.png
+SourceArt/**/*.tga
+
+# Binary Files
+Binaries/*
+Plugins/*/Binaries/*
+
+# Builds
+Build/*
+
+# Whitelist PakBlacklist-<BuildConfiguration>.txt files
+!Build/*/
+Build/*/**
+!Build/*/PakBlacklist*.txt
+
+# Don't ignore icon files in Build
+!Build/**/*.ico
+
+# Built data for maps
+*_BuiltData.uasset
+
+# Configuration files generated by the Editor
+Saved/*
+
+# Compiled source files for the engine to use
+Intermediate/*
+Plugins/*/Intermediate/*
+
+# Cache files for the editor to use
+DerivedDataCache/*
\ No newline at end of file
diff --git a/Resources/Icon128.png b/Resources/Icon128.png
new file mode 100644
index 0000000000000000000000000000000000000000..26a268dbf061b39e58e9c8d86337d05d64a7b92e
Binary files /dev/null and b/Resources/Icon128.png differ
diff --git a/Source/DisplayClusterInput/DisplayClusterInput.Build.cs b/Source/DisplayClusterInput/DisplayClusterInput.Build.cs
new file mode 100644
index 0000000000000000000000000000000000000000..94937c4e78e282ecaa5db76f22a22cb20bd3be52
--- /dev/null
+++ b/Source/DisplayClusterInput/DisplayClusterInput.Build.cs
@@ -0,0 +1,53 @@
+using UnrealBuildTool;
+
+public class DisplayClusterInput : ModuleRules
+{
+  public DisplayClusterInput(ReadOnlyTargetRules Target) : base(Target)
+  {
+    PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+
+    PublicIncludePaths.AddRange(
+      new string[]
+      {
+
+      }
+      );
+
+
+    PrivateIncludePaths.AddRange(
+      new string[]
+      {
+
+      }
+      );
+
+
+    PublicDependencyModuleNames.AddRange(
+      new string[]
+      {
+        "Core",
+        "CoreUObject",
+        "DisplayCluster",
+        "Engine",
+        "InputCore",
+        "InputDevice"
+      }
+      );
+
+
+    PrivateDependencyModuleNames.AddRange(
+      new string[]
+      {
+
+      }
+      );
+
+
+    DynamicallyLoadedModuleNames.AddRange(
+      new string[]
+      {
+
+      }
+      );
+  }
+}
diff --git a/Source/DisplayClusterInput/Private/DisplayClusterInputDevice.cpp b/Source/DisplayClusterInput/Private/DisplayClusterInputDevice.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9eff51a9185bdb9f188478ad052338091b2a1136
--- /dev/null
+++ b/Source/DisplayClusterInput/Private/DisplayClusterInputDevice.cpp
@@ -0,0 +1,110 @@
+#include "DisplayClusterInputDevice.h"
+
+#include "CoreMinimal.h"
+#include "Features/IModularFeatures.h"
+#include "Input/IDisplayClusterInputManager.h"
+#include "IDisplayCluster.h"
+
+#include "DisplayClusterKeys.h"
+
+#define LOCTEXT_NAMESPACE "DisplayClusterInput"
+
+FDisplayClusterInputDevice::FDisplayClusterInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) : MessageHandler(InMessageHandler)
+{
+  AxisIndices   = TArray<const FKey*>
+  {
+    &FDisplayClusterKeys::AxisX,
+    &FDisplayClusterKeys::AxisY
+  };
+  ButtonIndices = TArray<const FKey*> 
+  { 
+    &FDisplayClusterKeys::Trigger, 
+    &FDisplayClusterKeys::Action1,
+    &FDisplayClusterKeys::Action2,
+    &FDisplayClusterKeys::Action3,
+    &FDisplayClusterKeys::Action4,
+    &FDisplayClusterKeys::Action5,
+    &FDisplayClusterKeys::Action6,
+    &FDisplayClusterKeys::Action7,
+    &FDisplayClusterKeys::Action8,
+    &FDisplayClusterKeys::Action9
+  };
+}
+FDisplayClusterInputDevice::~FDisplayClusterInputDevice()
+{
+
+}
+
+void FDisplayClusterInputDevice::PreInit             ()
+{
+  EKeys::AddMenuCategoryDisplayInfo("DisplayCluster", LOCTEXT("DisplayClusterSubCategory", "Display Cluster"), TEXT("GraphEditor.PadEvent_16x"));
+
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Trigger, LOCTEXT("DisplayClusterTrigger", "Display Cluster Trigger" ), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action1, LOCTEXT("DisplayClusterAction1", "Display Cluster Action 1"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action2, LOCTEXT("DisplayClusterAction2", "Display Cluster Action 2"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action3, LOCTEXT("DisplayClusterAction3", "Display Cluster Action 3"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action4, LOCTEXT("DisplayClusterAction4", "Display Cluster Action 4"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action5, LOCTEXT("DisplayClusterAction5", "Display Cluster Action 5"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action6, LOCTEXT("DisplayClusterAction6", "Display Cluster Action 6"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action7, LOCTEXT("DisplayClusterAction7", "Display Cluster Action 7"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action8, LOCTEXT("DisplayClusterAction8", "Display Cluster Action 8"), FKeyDetails::GamepadKey, "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::Action9, LOCTEXT("DisplayClusterAction9", "Display Cluster Action 9"), FKeyDetails::GamepadKey, "DisplayCluster"));
+
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::AxisX  , LOCTEXT("DisplayClusterAxisX"  , "Display Cluster X Axis"  ), FKeyDetails::FloatAxis , "DisplayCluster"));
+  EKeys::AddKey(FKeyDetails(FDisplayClusterKeys::AxisY  , LOCTEXT("DisplayClusterAxisY"  , "Display Cluster Y Axis"  ), FKeyDetails::FloatAxis , "DisplayCluster"));
+}
+
+bool FDisplayClusterInputDevice::Exec                (UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
+{
+  return false;
+}                 
+void FDisplayClusterInputDevice::SendControllerEvents()
+{
+  if (!IDisplayCluster::IsAvailable()) return;
+
+  auto InputManager = IDisplayCluster::Get().GetInputMgr();
+  
+  TArray<FString> AxisDeviceIds;
+  InputManager->GetAxisDeviceIds(AxisDeviceIds);
+  for (auto i = 0; i < AxisDeviceIds.Num(); ++i)
+  {
+    for (auto j = 0; j < AxisIndices.Num(); ++j)
+    {
+      float Value;
+      InputManager->GetAxis(AxisDeviceIds[i], j, Value);
+      if (Value != 0.0f) MessageHandler->OnControllerAnalog(AxisIndices[j]->GetFName(), i, Value);
+    }
+  }
+
+  TArray<FString> ButtonDeviceIds;
+  InputManager->GetButtonDeviceIds(ButtonDeviceIds);
+  for (auto i = 0; i < ButtonDeviceIds.Num(); ++i)
+  {
+    for (auto j = 0; j < ButtonIndices.Num(); ++j)
+    {
+      bool Pressed, Released;
+      InputManager->WasButtonPressed (ButtonDeviceIds[i], j, Pressed );
+      InputManager->WasButtonReleased(ButtonDeviceIds[i], j, Released);
+      if (Pressed ) MessageHandler->OnControllerButtonPressed (ButtonIndices[j]->GetFName(), i, false);
+      if (Released) MessageHandler->OnControllerButtonReleased(ButtonIndices[j]->GetFName(), i, false);
+    }
+  }
+}                 
+void FDisplayClusterInputDevice::SetChannelValue     (int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value)
+{
+  // Intentionally empty (no haptic support).
+} 
+void FDisplayClusterInputDevice::SetChannelValues    (int32 ControllerId, const FForceFeedbackValues& Values)
+{
+  // Intentionally empty (no haptic support).
+}
+void FDisplayClusterInputDevice::SetMessageHandler   (const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
+{
+  MessageHandler = InMessageHandler;
+}
+void FDisplayClusterInputDevice::Tick                (float DeltaTime)
+{
+  // Intentionally empty (everything is handled in SendControllerEvents above).
+}
+
+#undef LOCTEXT_NAMESPACE
\ No newline at end of file
diff --git a/Source/DisplayClusterInput/Private/DisplayClusterInputDevice.h b/Source/DisplayClusterInput/Private/DisplayClusterInputDevice.h
new file mode 100644
index 0000000000000000000000000000000000000000..c04c6f45b9a559d40b81de7d3c2806d454571475
--- /dev/null
+++ b/Source/DisplayClusterInput/Private/DisplayClusterInputDevice.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "IInputDevice.h"
+
+class FDisplayClusterInputDevice : public IInputDevice
+{
+public:
+  FDisplayClusterInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler);
+  virtual ~FDisplayClusterInputDevice();
+
+  static  void PreInit             ();
+
+	virtual bool Exec                (UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)                   override;
+	virtual void SendControllerEvents()                                                                       override;
+	virtual void SetChannelValue     (int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override;
+	virtual void SetChannelValues    (int32 ControllerId, const FForceFeedbackValues& Values)                 override;
+	virtual void SetMessageHandler   (const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)  override;
+	virtual void Tick                (float DeltaTime)                                                        override;
+
+private:
+  TSharedPtr<FGenericApplicationMessageHandler> MessageHandler;
+  TArray<const FKey*>                           AxisIndices   ;
+  TArray<const FKey*>                           ButtonIndices ;
+};
\ No newline at end of file
diff --git a/Source/DisplayClusterInput/Private/DisplayClusterInputModule.cpp b/Source/DisplayClusterInput/Private/DisplayClusterInputModule.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8f69d94020cf7d28e0b231961f63ad9b57e5ce78
--- /dev/null
+++ b/Source/DisplayClusterInput/Private/DisplayClusterInputModule.cpp
@@ -0,0 +1,22 @@
+#include "DisplayClusterInputModule.h"
+
+#include "IDisplayCluster.h"
+
+#include "DisplayClusterInputDevice.h"
+
+#define LOCTEXT_NAMESPACE "DisplayClusterInput"
+
+void FDisplayClusterInputModule::StartupModule()
+{
+  IInputDeviceModule::StartupModule();
+  FDisplayClusterInputDevice::PreInit();
+}
+
+TSharedPtr<class IInputDevice> FDisplayClusterInputModule::CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler)
+{
+  return TSharedPtr<FDisplayClusterInputDevice>(new FDisplayClusterInputDevice(InMessageHandler));
+}
+
+#undef LOCTEXT_NAMESPACE
+
+IMPLEMENT_MODULE(FDisplayClusterInputModule, DisplayClusterInput)
diff --git a/Source/DisplayClusterInput/Private/DisplayClusterInputModule.h b/Source/DisplayClusterInput/Private/DisplayClusterInputModule.h
new file mode 100644
index 0000000000000000000000000000000000000000..f78b17b36d62a021bbc66a34e8b0c5df1b4fcb2a
--- /dev/null
+++ b/Source/DisplayClusterInput/Private/DisplayClusterInputModule.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "Templates/SharedPointer.h"
+#include "IInputDevice.h"
+
+#include "IDisplayClusterInputModule.h"
+
+class FDisplayClusterInputModule : public IDisplayClusterInputModule
+{
+  virtual void                           StartupModule    ()                                                                      override;
+  virtual TSharedPtr<class IInputDevice> CreateInputDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;
+};
\ No newline at end of file
diff --git a/Source/DisplayClusterInput/Private/DisplayClusterKeys.cpp b/Source/DisplayClusterInput/Private/DisplayClusterKeys.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8a6da501f20c84d569e4f6ac9b7c08e8d7255698
--- /dev/null
+++ b/Source/DisplayClusterInput/Private/DisplayClusterKeys.cpp
@@ -0,0 +1,15 @@
+#include "DisplayClusterKeys.h"
+
+const FKey FDisplayClusterKeys::Trigger("DisplayClusterTrigger");
+const FKey FDisplayClusterKeys::Action1("DisplayClusterAction1");
+const FKey FDisplayClusterKeys::Action2("DisplayClusterAction2");
+const FKey FDisplayClusterKeys::Action3("DisplayClusterAction3");
+const FKey FDisplayClusterKeys::Action4("DisplayClusterAction4");
+const FKey FDisplayClusterKeys::Action5("DisplayClusterAction5");
+const FKey FDisplayClusterKeys::Action6("DisplayClusterAction6");
+const FKey FDisplayClusterKeys::Action7("DisplayClusterAction7");
+const FKey FDisplayClusterKeys::Action8("DisplayClusterAction8");
+const FKey FDisplayClusterKeys::Action9("DisplayClusterAction9");
+
+const FKey FDisplayClusterKeys::AxisX  ("DisplayClusterAxisX"  );
+const FKey FDisplayClusterKeys::AxisY  ("DisplayClusterAxisY"  );
diff --git a/Source/DisplayClusterInput/Private/DisplayClusterKeys.h b/Source/DisplayClusterInput/Private/DisplayClusterKeys.h
new file mode 100644
index 0000000000000000000000000000000000000000..f91cc6da6a0f3061d41b484a6493e028f7996809
--- /dev/null
+++ b/Source/DisplayClusterInput/Private/DisplayClusterKeys.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "InputCoreTypes.h"
+
+struct FDisplayClusterKeys
+{
+  static const FKey Trigger;
+  static const FKey Action1;
+  static const FKey Action2;
+  static const FKey Action3;
+  static const FKey Action4;
+  static const FKey Action5;
+  static const FKey Action6;
+  static const FKey Action7;
+  static const FKey Action8;
+  static const FKey Action9;
+
+  static const FKey AxisX  ;
+  static const FKey AxisY  ;
+};
\ No newline at end of file
diff --git a/Source/DisplayClusterInput/Public/IDisplayClusterInputModule.h b/Source/DisplayClusterInput/Public/IDisplayClusterInputModule.h
new file mode 100644
index 0000000000000000000000000000000000000000..c1bcff4a1ff7775cfa347a51522eb6ee6090fb13
--- /dev/null
+++ b/Source/DisplayClusterInput/Public/IDisplayClusterInputModule.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "Modules/ModuleManager.h"
+#include "IInputDeviceModule.h"
+
+class IDisplayClusterInputModule : public IInputDeviceModule
+{
+public:
+	static inline IDisplayClusterInputModule& Get()
+	{
+		return FModuleManager::LoadModuleChecked<IDisplayClusterInputModule>("DisplayClusterInput");
+	}
+
+	static inline bool IsAvailable()
+	{
+		return FModuleManager::Get().IsModuleLoaded("DisplayClusterInput");
+	}
+};
diff --git a/nDisplayInput.uplugin b/nDisplayInput.uplugin
new file mode 100644
index 0000000000000000000000000000000000000000..cb78bd260c05f2a44dc92d133e5474171ca36715
--- /dev/null
+++ b/nDisplayInput.uplugin
@@ -0,0 +1,30 @@
+{
+  "FileVersion": 3,
+  "Version": 1,
+  "VersionName": "1.0",
+  "FriendlyName": "nDisplayInput",
+  "Description": "",
+  "Category": "Other",
+  "CreatedBy": "",
+  "CreatedByURL": "",
+  "DocsURL": "",
+  "MarketplaceURL": "",
+  "SupportURL": "",
+  "CanContainContent": true,
+  "IsBetaVersion": false,
+  "Installed": false,
+  "EnabledByDefault": true,
+  "Modules": [
+    {
+      "Name": "DisplayClusterInput",
+      "Type": "Developer",
+      "LoadingPhase": "Default"
+    }
+  ],
+  "Plugins": [
+    {
+      "Name": "nDisplay",
+      "Enabled": true
+    }
+  ]
+}
\ No newline at end of file