From 69d59ec862dc3dbb8f24ad19c2334f3deab52dfa Mon Sep 17 00:00:00 2001
From: Sebastian Pape <Sebastian.Pape@rwth-aachen.de>
Date: Wed, 16 Oct 2019 16:16:02 +0200
Subject: [PATCH] Refactored and renamed all classes to not have "CAVE" in
 their name, since this plugin also works with ROLV and Desktop

---
 NDisplayLaunchButton.uplugin                  |  23 +++
 Resources/NDisplay_40x.png                    | Bin 0 -> 384 bytes
 .../NDisplayLaunchButton.Build.cs             |  59 ++++++
 .../Private/NDisplayLaunchButton.cpp          | 168 ++++++++++++++++++
 .../Private/NDisplayLaunchButtonCommands.cpp  |  13 ++
 .../Private/NDisplayLaunchButtonStyle.cpp     |  56 ++++++
 .../Private/SocketHelper.h                    |  65 +++++++
 .../Public/NDisplayLaunchButton.h             |  29 +++
 .../Public/NDisplayLaunchButtonCommands.h     |  23 +++
 .../Public/NDisplayLaunchButtonSettings.h     | 116 ++++++++++++
 .../Public/NDisplayLaunchButtonStyle.h        |  31 ++++
 11 files changed, 583 insertions(+)
 create mode 100644 NDisplayLaunchButton.uplugin
 create mode 100644 Resources/NDisplay_40x.png
 create mode 100644 Source/NDisplayLaunchButton/NDisplayLaunchButton.Build.cs
 create mode 100644 Source/NDisplayLaunchButton/Private/NDisplayLaunchButton.cpp
 create mode 100644 Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonCommands.cpp
 create mode 100644 Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonStyle.cpp
 create mode 100644 Source/NDisplayLaunchButton/Private/SocketHelper.h
 create mode 100644 Source/NDisplayLaunchButton/Public/NDisplayLaunchButton.h
 create mode 100644 Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonCommands.h
 create mode 100644 Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonSettings.h
 create mode 100644 Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonStyle.h

diff --git a/NDisplayLaunchButton.uplugin b/NDisplayLaunchButton.uplugin
new file mode 100644
index 0000000..988d95d
--- /dev/null
+++ b/NDisplayLaunchButton.uplugin
@@ -0,0 +1,23 @@
+{
+	"FileVersion": 3,
+	"Version": 1,
+	"VersionName": "1.0",
+	"FriendlyName": "NDisplayLaunchButton",
+	"Description": "",
+	"Category": "Other",
+	"CreatedBy": "",
+	"CreatedByURL": "",
+	"DocsURL": "",
+	"MarketplaceURL": "",
+	"SupportURL": "",
+	"CanContainContent": true,
+	"IsBetaVersion": false,
+	"EnabledByDefault" : true,
+	"Modules": [
+		{
+			"Name": "NDisplayLaunchButton",
+			"Type": "Editor",
+			"LoadingPhase": "Default"
+		}
+	]
+}
\ No newline at end of file
diff --git a/Resources/NDisplay_40x.png b/Resources/NDisplay_40x.png
new file mode 100644
index 0000000000000000000000000000000000000000..810021b85a32e7e5129a3e0872474ccef6e6a0c1
GIT binary patch
literal 384
zcmeAS@N?(olHy`uVBq!ia0y~yV9)?z4mJh`hMs>rav2yH*pj^6T^Rm@;DWu&Co?cG
zntHl8hD5Z!oxazL#ZaX6{JFFL_VljcDBITkTcD{obe8J}$s<XL)~i|L4|r|da4Rb?
z<zsHj9G^96Y7cZ$-i5mDh!<6xe0X}&vm%Dp;|n6!Y|9I@ir^1OYmgS2_WZX=|NQfL
zapLRNt@%E)lG#FObIlH$S<DTYW?t;o+rls0*dqV0v!AQvdt5X_=YrMR)m23f%9F(-
z>cYP4xYxF4(ajGI+hiDgcpfb7ZnNpnv1E|o<=ylBVZx+>-(k<C-#z&Evvux7jyM^y
z-1l#nyiM{CKR)-<vg@-qyi5DizCuA<^vR;8n2-tH?^)|h#f~d`2maGuIN77LgKcG>
z@V=y#e1h}1OXs}zQ@Wz)G%w(|r;>`5OV5#dQ^6@O1Pp~_mw#XE$GQ_{OqKqO9;=sp
gigydA=+!VDTvsW6zqNfW0|Nttr>mdKI;Vst08(0|&Hw-a

literal 0
HcmV?d00001

diff --git a/Source/NDisplayLaunchButton/NDisplayLaunchButton.Build.cs b/Source/NDisplayLaunchButton/NDisplayLaunchButton.Build.cs
new file mode 100644
index 0000000..2406882
--- /dev/null
+++ b/Source/NDisplayLaunchButton/NDisplayLaunchButton.Build.cs
@@ -0,0 +1,59 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class NDisplayLaunchButton : ModuleRules
+{
+	public NDisplayLaunchButton(ReadOnlyTargetRules Target) : base(Target)
+	{
+		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
+		
+		PublicIncludePaths.AddRange(
+			new string[] {
+				// ... add public include paths required here ...
+			}
+			);
+				
+		
+		PrivateIncludePaths.AddRange(
+			new string[] {
+				// ... add other private include paths required here ...
+			}
+			);
+			
+		
+		PublicDependencyModuleNames.AddRange(
+			new string[]
+			{
+				"Core",
+				// ... add other public dependencies that you statically link with here ...
+			}
+			);
+			
+		
+		PrivateDependencyModuleNames.AddRange(
+			new string[]
+			{
+				"Projects",
+				"InputCore",
+				"UnrealEd",
+				"LevelEditor",
+				"CoreUObject",
+				"Engine",
+				"Slate",
+				"SlateCore",
+				"Sockets",
+				"Networking"
+				// ... add private dependencies that you statically link with here ...	
+			}
+			);
+		
+		
+		DynamicallyLoadedModuleNames.AddRange(
+			new string[]
+			{
+				// ... add any modules that your module loads dynamically here ...
+			}
+			);
+	}
+}
diff --git a/Source/NDisplayLaunchButton/Private/NDisplayLaunchButton.cpp b/Source/NDisplayLaunchButton/Private/NDisplayLaunchButton.cpp
new file mode 100644
index 0000000..a71a7b6
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Private/NDisplayLaunchButton.cpp
@@ -0,0 +1,168 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+#include "NDisplayLaunchButton.h"
+
+#include "NDisplayLaunchButtonStyle.h"
+#include "NDisplayLaunchButtonSettings.h"
+#include "NDisplayLaunchButtonCommands.h"
+#include "Misc/MessageDialog.h"
+#include "Framework/MultiBox/MultiBoxBuilder.h"
+#include "IPluginManager.h"
+#include "Misc/EngineVersion.h"
+#include "Networking.h"
+#include "SocketHelper.h"
+#include "LevelEditor.h"
+#include "FileHelpers.h"
+
+DEFINE_LOG_CATEGORY(LogNDisplayLaunchButton);
+
+void FNDisplayLaunchButtonModule::StartupModule()
+{
+	const UNDisplayLaunchButtonSettings* Settings = GetDefault<UNDisplayLaunchButtonSettings>();
+	FNDisplayLaunchButtonStyle::Initialize();
+	FNDisplayLaunchButtonStyle::ReloadTextures();
+	FNDisplayLaunchButtonCommands::Register();
+
+	TSharedPtr<class FUICommandList> PluginCommands = MakeShareable(new FUICommandList);
+	PluginCommands->MapAction(
+		FNDisplayLaunchButtonCommands::Get().PluginAction,
+		FExecuteAction::CreateRaw(this, &FNDisplayLaunchButtonModule::PluginButtonClicked),
+		FCanExecuteAction());
+
+	FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
+	TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
+	ToolbarExtender->AddToolBarExtension("Game", EExtensionHook::First, PluginCommands, FToolBarExtensionDelegate::CreateLambda([](FToolBarBuilder& Builder)
+	{
+		Builder.AddToolBarButton(FNDisplayLaunchButtonCommands::Get().PluginAction);
+	}));
+	LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);	
+}
+
+void FNDisplayLaunchButtonModule::ShutdownModule()
+{
+	FNDisplayLaunchButtonStyle::Shutdown();
+	FNDisplayLaunchButtonCommands::Unregister();
+}
+
+void FNDisplayLaunchButtonModule::PluginButtonClicked()
+{
+	const UNDisplayLaunchButtonSettings* Settings = GetDefault<UNDisplayLaunchButtonSettings>();
+	if (Settings->LaunchType == NONE)
+	{
+		GEngine->AddOnScreenDebugMessage(-1, 3, FColor::White, TEXT("The Button is set to do nothing."));
+		return;
+	}
+
+	if (!UEditorLoadingAndSavingUtils::SaveDirtyPackagesWithDialog(true, true)) return;
+	
+	// minimize the root window to provide max performance for the preview.
+	TSharedPtr<SWindow> RootWindow = FGlobalTabmanager::Get()->GetRootWindow();
+	if (RootWindow.IsValid()) RootWindow->Minimize();
+	
+	if (Settings->LaunchType == ROLV)
+	{
+		FString EditorExecutable = "UE4Editor.exe";
+		FString Parameters = "\"" + FPaths::GetProjectFilePath() + "\" -game dc_cfg=\"" + Settings->RolvConfig.FilePath + "\"" + ((Settings->ROLVLogToProjectDir) ? ("-log ABSLOG=" + FPaths::ProjectDir() + "\\ROLV_Launch.log") : "");
+
+		FProcHandle VRPN;
+		ProjectorDisplayType ModeFromBefore = DisplayType_Error;
+		if (Settings->StartVRPN) VRPN = FPlatformProcess::CreateProc(*Settings->VRPNPath.FilePath, *FString("-f \"" + Settings->VRPNConfigPath.FilePath + "\" -millisleep 0"), false, false, false, nullptr, 0, nullptr, nullptr);
+		if (Settings->StartDTrack) SendToDTrack(Settings->DTrackIP, Settings->DTrackPort, "dtrack2 tracking start\0");
+		if (Settings->SwitchProjector) ModeFromBefore = SwitchProjectorToState(Settings->ProjectorIP, Settings->ProjectorPort, Settings->ProjectorType);
+
+		FProcHandle Instance = FPlatformProcess::CreateProc(*EditorExecutable, *(Parameters + " " + Settings->ROLVLaunchParameters), true, false, false, nullptr, 0, nullptr, nullptr);
+		FPlatformProcess::WaitForProc(Instance);
+
+		if (Settings->StartVRPN) FPlatformProcess::TerminateProc(VRPN);
+		FPlatformProcess::CloseProc(VRPN);
+		if (Settings->StartDTrack) SendToDTrack(Settings->DTrackIP, Settings->DTrackPort, "dtrack2 tracking stop\0");
+		if (Settings->SwitchProjector && ModeFromBefore != DisplayType_Error) SwitchProjectorToState(Settings->ProjectorIP, Settings->ProjectorPort, ModeFromBefore);
+	}
+
+	if (Settings->LaunchType == MiniCAVE)
+	{
+		FString Config = IPluginManager::Get().FindPlugin("CAVELaunchButton")->GetBaseDir() + "/LaunchConfig/minicave.cfg";
+		FString EditorExecutable = "UE4Editor.exe";
+		FString Parameters = "\"" + FPaths::GetProjectFilePath() + "\" -game dc_cfg=\"" + Config + "\" " + Settings->MiniCAVELaunchParameters;
+
+		const int Num_Nodes = 5;
+		FString Windows_Node_Specific_Commands[Num_Nodes] = {
+			"dc_node=node_floor WinX=720  WinY=300 ResX=480 ResY=480" + ((Settings->MiniCAVELogToProjectDir) ? (" -log ABSLOG=" + FPaths::ProjectDir() + "\\MiniCaveMulti.log") : "") + " " + Settings->MiniCAVEAdditionalLaunchParametersMaster,
+			"dc_node=node_front WinX=720  WinY=0   ResX=480 ResY=300",
+			"dc_node=node_left  WinX=420  WinY=300 ResX=300 ResY=480",
+			"dc_node=node_right WinX=1200 WinY=300 ResX=300 ResY=480",
+			"dc_node=node_back  WinX=720  WinY=780 ResX=480 ResY=300"
+		};
+
+		FProcHandle Processes[Num_Nodes];
+		for (int i = 0; i < Num_Nodes; i++)
+		{
+			Processes[i] = FPlatformProcess::CreateProc(*EditorExecutable, *(Parameters + " " + Windows_Node_Specific_Commands[i]), true, false, false, nullptr, 0, nullptr, nullptr);
+		}
+		FPlatformProcess::WaitForProc(Processes[Num_Nodes - 1]); //wait for only one of them
+	}
+
+	if (Settings->LaunchType == CAVE)
+	{
+		FProcHandle Instance = FPlatformProcess::CreateProc(
+			*Settings->CAVELaunchScriptPath.FilePath,
+			*("\"" + (FPaths::ConvertRelativePathToFull(".") + "/UE4Editor\"")
+				+ " " + FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath())
+				+ " " + FString::FromInt(FEngineVersion::Current().GetMajor())
+				+ FString::FromInt(FEngineVersion::Current().GetMinor())),
+			true, false, false, nullptr, 0, nullptr, nullptr);
+		FPlatformProcess::WaitForProc(Instance);
+	}
+
+	if (RootWindow.IsValid()) RootWindow->Maximize();
+}
+
+void FNDisplayLaunchButtonModule::SendToDTrack(FString Address, int Port, FString Message)
+{
+	FSocket* Socket = USocketHelper::OpenSocket(Address, Port, "DTrackSocket");
+	if (!Socket) return;
+	if (USocketHelper::SendSocket(Socket, Message) <= 0) return;
+	FString Response = USocketHelper::ReceiveSocket<100>(Socket);
+	if (Response.Compare("dtrack2 ok") != 0)
+	{
+		UE_LOG(LogTemp, Error, TEXT("DTrack Command Failed. Response: '%s'"), *Response);
+	}
+	Socket->Shutdown(ESocketShutdownMode::ReadWrite);
+	Socket->Close();
+}
+
+//Returns old state
+ProjectorDisplayType FNDisplayLaunchButtonModule::SwitchProjectorToState(FString Address, int Port, ProjectorDisplayType State)
+{
+	ProjectorDisplayType ModeBefore = ProjectorDisplayType::DisplayType_Error;
+	FSocket* Socket = USocketHelper::OpenSocket(Address, Port, "ProjectorSocket");
+	if (!Socket) return ModeBefore;
+
+	//Get mode from before
+	if (USocketHelper::SendSocket(Socket, ":TDSM ?\r") <= 0) return ModeBefore;
+	FString Response = USocketHelper::ReceiveSocket<100>(Socket); //Response looks like: %001 TDSM 000000
+
+	if (!Response.IsEmpty())
+	{
+		int32 Position = 0;
+		Response.FindLastChar(' ', Position);
+		ModeBefore = static_cast<ProjectorDisplayType>(FCString::Atoi(*Response.RightChop(Position)));
+	}
+
+	if (Response.IsEmpty() || USocketHelper::SendSocket(Socket, ":TDSM " + FString::FromInt(State) + "\r") <= 0)
+	{
+		Socket->Shutdown(ESocketShutdownMode::ReadWrite);
+		Socket->Close();
+		return ModeBefore;
+	}
+	Response = USocketHelper::ReceiveSocket<100>(Socket);
+	if (Response.EndsWith(")"))
+	{
+		//Errors are marked like this
+		UE_LOG(LogTemp, Error, TEXT("Projector Type Change Failed. Response: '%s'"), *Response);
+	}
+	Socket->Shutdown(ESocketShutdownMode::ReadWrite);
+	Socket->Close();
+	return ModeBefore;
+}
+
+IMPLEMENT_MODULE(FNDisplayLaunchButtonModule, NDisplayLaunchButton)
diff --git a/Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonCommands.cpp b/Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonCommands.cpp
new file mode 100644
index 0000000..230f98f
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonCommands.cpp
@@ -0,0 +1,13 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "NDisplayLaunchButtonCommands.h"
+#include "NDisplayLaunchButtonSettings.h"
+
+#define LOCTEXT_NAMESPACE "FNDisplayLaunchButtonModule"
+
+void FNDisplayLaunchButtonCommands::RegisterCommands()
+{
+	UI_COMMAND(PluginAction, "N-Display", "Launch current project via N-Display", EUserInterfaceActionType::Button, FInputChord());
+}
+
+#undef LOCTEXT_NAMESPACE
diff --git a/Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonStyle.cpp b/Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonStyle.cpp
new file mode 100644
index 0000000..82a9ae4
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Private/NDisplayLaunchButtonStyle.cpp
@@ -0,0 +1,56 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#include "NDisplayLaunchButtonStyle.h"
+#include "NDisplayLaunchButton.h"
+#include "Framework/Application/SlateApplication.h"
+#include "Styling/SlateStyleRegistry.h"
+#include "Slate/SlateGameResources.h"
+#include "Interfaces/IPluginManager.h"
+
+TSharedPtr< FSlateStyleSet > FNDisplayLaunchButtonStyle::StyleInstance = NULL;
+
+void FNDisplayLaunchButtonStyle::Initialize()
+{
+	if (!StyleInstance.IsValid())
+	{
+		StyleInstance = Create();
+		FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
+	}
+}
+
+void FNDisplayLaunchButtonStyle::Shutdown()
+{
+	FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
+	ensure(StyleInstance.IsUnique());
+	StyleInstance.Reset();
+}
+
+FName FNDisplayLaunchButtonStyle::GetStyleSetName()
+{
+	static FName StyleSetName(TEXT("NDisplayLaunchButtonStyle"));
+	return StyleSetName;
+}
+
+TSharedRef< FSlateStyleSet > FNDisplayLaunchButtonStyle::Create()
+{
+	TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("NDisplayLaunchButtonStyle"));
+	Style->SetContentRoot(IPluginManager::Get().FindPlugin("NDisplayLaunchButton")->GetBaseDir() / TEXT("Resources"));
+
+	const FVector2D Icon40x40(40.0f, 40.0f);
+	Style->Set("NDisplayLaunchButton.PluginAction", new FSlateImageBrush(Style->RootToContentDir(TEXT("NDisplay_40x"), TEXT(".png")), Icon40x40));;
+	
+	return Style;
+}
+
+void FNDisplayLaunchButtonStyle::ReloadTextures()
+{
+	if (FSlateApplication::IsInitialized())
+	{
+		FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
+	}
+}
+
+const ISlateStyle& FNDisplayLaunchButtonStyle::Get()
+{
+	return *StyleInstance;
+}
diff --git a/Source/NDisplayLaunchButton/Private/SocketHelper.h b/Source/NDisplayLaunchButton/Private/SocketHelper.h
new file mode 100644
index 0000000..1384bff
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Private/SocketHelper.h
@@ -0,0 +1,65 @@
+#pragma once
+#include <string>
+
+#include "CoreMinimal.h"
+#include "Sockets.h"
+#include "IPv4Address.h"
+#include "IPv4Endpoint.h"
+#include "TcpSocketBuilder.h"
+#include "SocketHelper.generated.h"
+
+DECLARE_LOG_CATEGORY_CLASS(LogNDisplayLaunchButtonSockerHelper, Log, All);
+
+UCLASS()
+class USocketHelper : public UObject{
+	GENERATED_BODY()
+
+public:
+	
+	static FSocket* OpenSocket(FString Address, int Port, FString SocketName) {
+		FIPv4Address ParsedAddress;
+		if (!FIPv4Address::Parse(Address, ParsedAddress)) {
+			UE_LOG(LogNDisplayLaunchButtonSockerHelper, Error, TEXT("Could not parse Address %s"), *Address);
+			return nullptr;
+		}
+
+		FIPv4Endpoint Endpoint(ParsedAddress, Port);
+		FSocket* Socket = FTcpSocketBuilder(*SocketName);
+		if (!Socket->Connect(Endpoint.ToInternetAddr().Get()))
+		{
+			UE_LOG(LogNDisplayLaunchButtonSockerHelper, Error, TEXT("Error connecting to server %s:%d via %s"), *Address, Port, *SocketName);
+			Socket->Close();
+			return nullptr;
+		}
+		return Socket;
+	}
+
+	static int32 SendSocket(FSocket* Socket, FString Message) {
+		Socket->Wait(ESocketWaitConditions::WaitForWrite, FTimespan::FromSeconds(5));
+		std::string Buffer = std::string(TCHAR_TO_UTF8(*Message));
+		int32 Sent = 0;
+		if (!Socket->Send(reinterpret_cast<const uint8 *>(Buffer.c_str()), Buffer.length(), Sent))
+		{
+			UE_LOG(LogNDisplayLaunchButtonSockerHelper, Error, TEXT("Error sending via %s. Sent %d bytes"), *Socket->GetDescription(), Sent);
+			Socket->Close();
+		}
+		return Sent;
+	}
+	
+	template<int32 BufferSize> static FString ReceiveSocket(FSocket* Socket) {
+		Socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(5));
+		char ReceiveBuffer[BufferSize];
+		int BytesReceived = 0;
+		if (!Socket->Recv(reinterpret_cast<uint8*>(&ReceiveBuffer), BufferSize, BytesReceived)) {
+			UE_LOG(LogNDisplayLaunchButtonSockerHelper, Error, TEXT("No valid response from %s. Response: '%s'"), *Socket->GetDescription(), *FString(std::string(ReceiveBuffer, BytesReceived).c_str()));
+			Socket->Close();
+		}
+		if (BytesReceived <= 0) return FString("");
+		return FString(std::string(ReceiveBuffer, BytesReceived).c_str());
+	}
+	
+	static void CloseSocket(FSocket* Socket) {
+		Socket->Shutdown(ESocketShutdownMode::ReadWrite);
+		Socket->Close();
+	}
+};
diff --git a/Source/NDisplayLaunchButton/Public/NDisplayLaunchButton.h b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButton.h
new file mode 100644
index 0000000..5a228c5
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButton.h
@@ -0,0 +1,29 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleManager.h"
+#include "NDisplayLaunchButtonSettings.h"
+#include "LogMacros.h"
+#include "Framework/MultiBox/MultiBoxBuilder.h"
+
+class FToolBarBuilder;
+class FMenuBuilder;
+
+DECLARE_LOG_CATEGORY_EXTERN(LogNDisplayLaunchButton, Log, All);
+
+class FNDisplayLaunchButtonModule : public IModuleInterface
+{
+public:
+
+	/** IModuleInterface implementation */
+	virtual void StartupModule() override;
+	virtual void ShutdownModule() override;
+
+	/** This function will be bound to Command. */
+	void PluginButtonClicked();
+
+	void SendToDTrack(FString Address, int Port, FString Message);
+	ProjectorDisplayType SwitchProjectorToState(FString Address, int Port, ProjectorDisplayType State);
+};
diff --git a/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonCommands.h b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonCommands.h
new file mode 100644
index 0000000..5c2f355
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonCommands.h
@@ -0,0 +1,23 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Framework/Commands/Commands.h"
+#include "NDisplayLaunchButtonStyle.h"
+
+class FNDisplayLaunchButtonCommands : public TCommands<FNDisplayLaunchButtonCommands>
+{
+public:
+
+	FNDisplayLaunchButtonCommands()
+		: TCommands<FNDisplayLaunchButtonCommands>(TEXT("NDisplayLaunchButton"), NSLOCTEXT("Contexts", "NDisplayLaunchButton", "NDisplayLaunchButton Plugin"), NAME_None, FNDisplayLaunchButtonStyle::GetStyleSetName())
+	{
+	}
+
+	// TCommands<> interface
+	virtual void RegisterCommands() override;
+
+public:
+	TSharedPtr< FUICommandInfo > PluginAction;
+};
diff --git a/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonSettings.h b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonSettings.h
new file mode 100644
index 0000000..160ed3c
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonSettings.h
@@ -0,0 +1,116 @@
+#pragma once
+#include "CoreMinimal.h"
+#include "Engine/EngineTypes.h"
+#include "Engine/DeveloperSettings.h"
+#include "NDisplayLaunchButtonSettings.generated.h"
+
+UENUM(BlueprintType)
+enum ProjectorDisplayType
+{
+	DisplayType_Off = 0 UMETA(DisplayName = "Off"),
+	DisplayType_Frame_Sequential = 1 UMETA(DisplayName = "Frame Sequential"),
+	DisplayType_Side_By_Side = 2 UMETA(DisplayName = "Side By Side"),
+	DisplayType_DualHead = 3 UMETA(DisplayName = "Dual Head"),
+	DisplayType_Error = 4 UMETA(Hidden)
+};
+
+UENUM(BlueprintType)
+enum ButtonLaunchType
+{
+	NONE UMETA(DisplayName = "Nothing"),
+	MiniCAVE UMETA(DisplayName = "MiniCAVE"),
+	CAVE UMETA(DisplayName = "CAVE"),
+	ROLV UMETA(DisplayName = "ROLV")
+};
+
+UCLASS(config=Engine, defaultconfig, meta=(DisplayName="nDisplay Launch Button"))
+class UNDisplayLaunchButtonSettings : public UDeveloperSettings
+{
+	GENERATED_BODY()
+
+public:
+	UPROPERTY(EditAnywhere, config, Category = "General", meta = (DisplayName = "Start "))
+	TEnumAsByte<ButtonLaunchType> LaunchType;
+
+	/*
+	 * Mini CAVE Options
+	 */
+	UPROPERTY(EditAnywhere, config, Category = "General|MiniCAVE", meta = (DisplayName = "Launch Parameters"))
+	FString MiniCAVELaunchParameters = "-dc_cluster -dc_dev_mono -windowed -fixedseed -notexturestreaming -opengl4";
+	UPROPERTY(EditAnywhere, config, Category = "General|MiniCAVE", meta = (DisplayName = "Additioanl Launch Parameters for Master"))
+	FString MiniCAVEAdditionalLaunchParametersMaster = "";
+	UPROPERTY(EditAnywhere, config, Category = "General|MiniCAVE", meta = (DisplayName = "Write Log to Project Directory"))
+	bool MiniCAVELogToProjectDir = true;
+
+	/*
+	 * CAVE Options
+	 */
+	UPROPERTY(EditAnywhere, config, Category = "General|CAVE", meta = (DisplayName = "Path to Launch Script"))
+	FFilePath CAVELaunchScriptPath;
+
+	/*
+	 * ROLV Options
+	 */
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV", meta = (DisplayName = "Path to the ROLV nDisplay Config"))
+	FFilePath RolvConfig;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV", meta = (DisplayName = "Launch Parameters"))
+	FString ROLVLaunchParameters = "-dc_cluster -nosplash -fixedseed -dx11 -dc_dev_side_by_side -notexturestreaming -fullscreen dc_node=node_main";
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV", meta = (DisplayName = "Write Log to Project Directory"))
+	bool ROLVLogToProjectDir = true;
+
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|Projector", meta = (DisplayName = "Change Projector Mode on Startup"))
+	bool SwitchProjector = true;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|Projector", meta = (DisplayName = "Switch to "))
+	TEnumAsByte<ProjectorDisplayType> ProjectorType;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|Projector", meta = (DisplayName = "Projector IP"))
+	FString ProjectorIP;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|Projector", meta = (DisplayName = "Projector Port"))
+	int ProjectorPort = 1025;
+
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|VRPN", meta = (DisplayName = "Start VRPN in the Background"))
+	bool StartVRPN = true;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|VRPN", meta = (DisplayName = "Path to VRPN Executable"))
+	FFilePath VRPNPath;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|VRPN", meta = (DisplayName = "Path to VRPN Config"))
+	FFilePath VRPNConfigPath;
+
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|DTRACK", meta = (DisplayName = "Start DTrack in the Background"))
+	bool StartDTrack = true;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|DTRACK", meta = (DisplayName = "DTrack IP"))
+	FString DTrackIP;
+	UPROPERTY(EditAnywhere, config, Category = "General|ROLV|DTRACK", meta = (DisplayName = "DTrack Port"))
+	int DTrackPort = 50105;
+
+	//Macro used to shorten code
+#define PROPERTY_CONDITION_CHECK(Variable, Condition) if (InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UNDisplayLaunchButtonSettings, Variable)) return Condition;
+
+	bool CanEditChange(const UProperty* InProperty) const override
+	{
+		const bool ParentVal = Super::CanEditChange(InProperty);
+
+		//TODO: This Code is not executed for all FFilePath Properties. Check if this works in 4.23 with the EditCondition Parser and remove this method
+		
+		PROPERTY_CONDITION_CHECK(MiniCAVELaunchParameters, LaunchType == MiniCAVE)
+		PROPERTY_CONDITION_CHECK(MiniCAVEAdditionalLaunchParametersMaster, LaunchType == MiniCAVE)
+		PROPERTY_CONDITION_CHECK(MiniCAVELogToProjectDir, LaunchType == MiniCAVE)
+		
+		PROPERTY_CONDITION_CHECK(CAVELaunchScriptPath, LaunchType == CAVE)
+		
+		PROPERTY_CONDITION_CHECK(RolvConfig, LaunchType == ROLV)
+		PROPERTY_CONDITION_CHECK(ROLVLaunchParameters, LaunchType == ROLV)
+		PROPERTY_CONDITION_CHECK(ROLVLogToProjectDir, LaunchType == ROLV)
+		PROPERTY_CONDITION_CHECK(SwitchProjector, LaunchType == ROLV)
+		PROPERTY_CONDITION_CHECK(ProjectorType, LaunchType == ROLV && SwitchProjector)
+		PROPERTY_CONDITION_CHECK(ProjectorIP, LaunchType == ROLV && SwitchProjector)
+		PROPERTY_CONDITION_CHECK(ProjectorPort, LaunchType == ROLV && SwitchProjector)
+		PROPERTY_CONDITION_CHECK(VRPNConfigPath, LaunchType == ROLV && StartVRPN)
+		PROPERTY_CONDITION_CHECK(StartVRPN, LaunchType == ROLV)
+		PROPERTY_CONDITION_CHECK(VRPNPath, LaunchType == ROLV && StartVRPN)
+		PROPERTY_CONDITION_CHECK(VRPNConfigPath, LaunchType == ROLV && StartVRPN)
+		PROPERTY_CONDITION_CHECK(StartDTrack, LaunchType == ROLV)
+		PROPERTY_CONDITION_CHECK(DTrackIP, LaunchType == ROLV && StartDTrack)
+		PROPERTY_CONDITION_CHECK(DTrackPort, LaunchType == ROLV && StartDTrack)
+		
+		return ParentVal;
+	}
+};
diff --git a/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonStyle.h b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonStyle.h
new file mode 100644
index 0000000..2ab4108
--- /dev/null
+++ b/Source/NDisplayLaunchButton/Public/NDisplayLaunchButtonStyle.h
@@ -0,0 +1,31 @@
+// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Styling/SlateStyle.h"
+
+class FNDisplayLaunchButtonStyle
+{
+public:
+
+	static void Initialize();
+
+	static void Shutdown();
+
+	/** reloads textures used by slate renderer */
+	static void ReloadTextures();
+
+	/** @return The Slate style set for the Shooter game */
+	static const ISlateStyle& Get();
+
+	static FName GetStyleSetName();
+	
+private:
+	
+	static TSharedRef< class FSlateStyleSet > Create();
+
+private:
+
+	static TSharedPtr< class FSlateStyleSet > StyleInstance;
+};
-- 
GitLab