diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f47f621bce..89d4155e9a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -213,7 +213,23 @@ Setting Up Your Environment
* Click on the resulting link to go to that class's documentation page.
* The Unity documentation for that class will start with some faint grey text at the top of the page that says, "Implemented in:", which tells you which DLL you need to reference to get code using that class to compile properly.
- 3. If you do not have a copy of KSP locally, you may
+ 3. Make sure your installation of KSP has LinuxGuruGamer's ClivkThroughBlocker
+ mod installed. kOS now needs it in order to compile. After it is
+ installed, copy its DLL file into Resources/ from your GameData folder.
+
+ Copy This file:
+
+ * $KSP/GameData/00_ClickThroughBlocker/Plugins/ClickThroughBlocker.dll
+
+ To here:
+
+ * Resources/
+
+ Then *make a Reference to Resoruces/ClickThroughBlocker.dll in your kOS project
+ file*. This is needed for it to let you compile parts of kOS that use classes
+ from ClickThroughBlocker.
+
+ 4. If you do not have a copy of KSP locally, you may
download dummy assemblies at https://github.com/KSP-KOS/KSP_LIB
3. Make sure you are targeting this version of .Net: ".Net 4.0 Framework".
diff --git a/src/kOS/Module/kOSCustomParameters.cs b/src/kOS/Module/kOSCustomParameters.cs
index 65d270b222..32bccdfbb6 100644
--- a/src/kOS/Module/kOSCustomParameters.cs
+++ b/src/kOS/Module/kOSCustomParameters.cs
@@ -1,6 +1,7 @@
using KSP.IO;
using System;
using System.Reflection;
+using System.Linq;
namespace kOS.Module
{
@@ -46,6 +47,9 @@ public static kOSCustomParameters Instance
[GameParameters.CustomIntParameterUI("")]
public int version = 0;
+ [GameParameters.CustomParameterUI("")]
+ public bool passedClickThroughCheck = false;
+
// these values constrain and back the InstructionsPerUpdate property so that it is clamped both in the
// user interface and when set from within a script.
private const int ipuMin = 50;
@@ -202,6 +206,95 @@ public void CheckMigrateSettings()
}
}
+ public void CheckClickThroughBlockerExists()
+ {
+ if (passedClickThroughCheck)
+ return;
+ bool clickThroughExists = false;
+
+ var loadedCTBAssembly = AssemblyLoader.loadedAssemblies.FirstOrDefault(a => a.dllName.Equals("ClickThroughBlocker"));
+ if (loadedCTBAssembly != null)
+ {
+ // Must be at least version 0.10 of ClickThroughBlocker:
+ if (loadedCTBAssembly.versionMajor > 0 || loadedCTBAssembly.versionMinor >= 10)
+ {
+ Type ctbType = loadedCTBAssembly.assembly.GetType("ClickThroughFix.CTB", false);
+ if (ctbType != null)
+ {
+ if (ctbType.GetField("focusFollowsclick") != null)
+ {
+ clickThroughExists = true;
+ }
+ }
+ }
+ }
+
+ string popupText =
+ "=======================================\n" +
+ "kOS is Checking for ClickThroughBlocker\n" +
+ "=======================================\n\n" +
+ "Starting with kOS v1.3, kOS has become dependent on the existence of the ClickThroughBlocker mod. " +
+ "(And it must be at least version 0.10 of ClickThroughBlocker.)\n\n";
+
+ if (clickThroughExists)
+ {
+ popupText +=
+ " <<<< CHECK SUCCEEDED >>>>>\n\n" +
+ "kOS has found ClickThroughBlocker installed, and it appears to be a version that will work with kOS.\n" +
+ "\n" +
+ "Please note that while in the past the kOS terminal has always been click-to-focus, from now " +
+ "on it will behave however ClickThroughBlocker is set to act, which may be focus-follows-mouse.\n" +
+ "You can use ClickThroughBlocker's settings to change this behvior like this:\n\n" +
+ "[Hit Escape] Settings ->\n" +
+ " Difficulty Options ->\n" +
+ " ClickThroughBlocker ->\n" +
+ " [x] Focus Follows Click\n\n";
+ }
+ else
+ {
+ popupText +=
+ " !!! CHECK FAILED !!!\n\n" +
+ "kOS couldn't find a version of ClickThroughBlocker that works with kOS. This could be " +
+ "because ClickThroughBlocker is not installed at all, or it could be because its version is too old " +
+ "(or too new, if ClickThroughBlocker ever renames some things that kOS is using).\n" +
+ "\n\n" +
+ "To use kOS v1.3 or higher you'll need to quit Kerbal Space Program and install a version of ClickThroughBlocker that it supports.\n";
+ }
+
+ string buttonText;
+ global::Callback clickThroughAck;
+ if (clickThroughExists)
+ {
+ clickThroughAck = AcceptClickThrough;
+ buttonText = "Acknowledged.";
+ }
+ else
+ {
+ clickThroughAck = FailedClickThrough;
+ buttonText = "Acknowledged. I'll have to quit and change my mods.";
+ }
+
+ kOSSettingsChecker.QueueDialog(
+ 0.75f, 0.6f,
+ new MultiOptionDialog(
+ "ClickThroughBlockerCheck",
+ popupText,
+ "kOS ClickThroughBlocker Check",
+ HighLogic.UISkin,
+ new DialogGUIButton(buttonText, clickThroughAck, true)
+ ));
+ }
+
+ public void AcceptClickThrough()
+ {
+ passedClickThroughCheck = true;
+ }
+
+ public void FailedClickThrough()
+ {
+ passedClickThroughCheck = false;
+ }
+
public void MigrateSettingsNormal()
{
MigrateSettings(false);
diff --git a/src/kOS/Module/kOSSettingsChecker.cs b/src/kOS/Module/kOSSettingsChecker.cs
index 89acb97f43..3e01f6be24 100644
--- a/src/kOS/Module/kOSSettingsChecker.cs
+++ b/src/kOS/Module/kOSSettingsChecker.cs
@@ -22,6 +22,7 @@ private void CheckSettings()
SafeHouse.Logger.SuperVerbose("kOSSettingsChecker.CheckSettings()");
HighLogic.CurrentGame.Parameters.CustomParams().CheckMigrateSettings();
HighLogic.CurrentGame.Parameters.CustomParams().CheckNewManagers();
+ HighLogic.CurrentGame.Parameters.CustomParams().CheckClickThroughBlockerExists();
}
// Because rapidly showing dialogs can prevent some from being shown, we can just queue up
diff --git a/src/kOS/Properties/AssemblyInfo.cs b/src/kOS/Properties/AssemblyInfo.cs
index 52fa1b3b68..f9de84228d 100644
--- a/src/kOS/Properties/AssemblyInfo.cs
+++ b/src/kOS/Properties/AssemblyInfo.cs
@@ -34,3 +34,8 @@
[assembly: AssemblyFileVersion("1.2.1.0")]
[assembly: AssemblyVersion("1.2.1.0")]
[assembly: KSPAssembly("kOS", 1, 7)]
+
+// No longer a hard dependancy, because we want to tell the user why kOS isn't working
+// if ClickThroughBlocker is not there, rather than just have it silently refuse to
+// load kOS with no explanation as would happen if this line was enabled:
+// [assembly: KSPAssemblyDependency("ClickThroughBlocker", 1, 0)]
\ No newline at end of file
diff --git a/src/kOS/Screen/DelegateDialog.cs b/src/kOS/Screen/DelegateDialog.cs
index 8836ac6e64..d8cce5fcf7 100644
--- a/src/kOS/Screen/DelegateDialog.cs
+++ b/src/kOS/Screen/DelegateDialog.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
using UnityEngine;
namespace kOS.Screen
@@ -38,7 +39,7 @@ public void OnGUI()
if (invoked)
{
float guessWidth = GUI.skin.label.CalcSize( new GUIContent(message) ).x;
- GUILayout.Window( parent.GetUniqueId()+1, new Rect( parent.GetRect().xMin+200,
+ ClickThruBlocker.GUILayoutWindow( parent.GetUniqueId()+1, new Rect( parent.GetRect().xMin+200,
parent.GetRect().yMin+10,
guessWidth,
0) , DrawConfirm, "Confirm", GUILayout.ExpandWidth(true) );
diff --git a/src/kOS/Screen/GUIWindow.cs b/src/kOS/Screen/GUIWindow.cs
index a5f6e30e8c..2d5e54ad9b 100644
--- a/src/kOS/Screen/GUIWindow.cs
+++ b/src/kOS/Screen/GUIWindow.cs
@@ -14,6 +14,7 @@
using kOS.Utilities;
using kOS.Communication;
using kOS.Safe;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
namespace kOS.Screen
{
@@ -116,7 +117,7 @@ void OnGUI()
GUI.skin = HighLogic.Skin;
- WindowRect = GUILayout.Window(UniqueId, WindowRect, WidgetGui, TitleText, style);
+ WindowRect = ClickThruBlocker.GUILayoutWindow(UniqueId, WindowRect, WidgetGui, TitleText, style);
if (currentPopup != null) {
var r = RectExtensions.EnsureCompletelyVisible(currentPopup.popupRect);
@@ -124,7 +125,7 @@ void OnGUI()
currentPopup.PopDown();
} else {
GUI.BringWindowToFront(UniqueId + 1);
- currentPopup.popupRect = GUILayout.Window(UniqueId + 1, r, PopupGui, "", style);
+ currentPopup.popupRect = ClickThruBlocker.GUILayoutWindow(UniqueId + 1, r, PopupGui, "", style);
}
}
}
diff --git a/src/kOS/Screen/KOSManagedWindow.cs b/src/kOS/Screen/KOSManagedWindow.cs
index 5759c8b67e..95e2496bf5 100644
--- a/src/kOS/Screen/KOSManagedWindow.cs
+++ b/src/kOS/Screen/KOSManagedWindow.cs
@@ -1,14 +1,23 @@
using System.Collections.Generic;
using UnityEngine;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
namespace kOS.Screen
{
///
/// kOSManagedWindow is for any Unity Monobehavior that you'd like to
- /// have contain a GUI.Window, and you need kOS to keep track of the
+ /// have contain an IMGUI Window, and you need kOS to keep track of the
/// window stacking for which click is on top of which other click.
/// Unity's built in systems for this don't work well at all, so
/// we had to make up our own.
+ ///
+ /// Issue #2697 - In addition to KOS's own system to handle this,
+ /// This also is now layered on top of Linuxgurugamer's ClickThroughBlocker
+ /// mod, so it uses its wrappers around GUI.Window. That was needed because
+ /// if SOME mods use ClickThruBlocker windows, then those windows will get first
+ /// dibs on events before kOS gets to, making kOS helpless to intercept events it's
+ /// trying to protect other windows from seeing. ClickThruBlocker is a mod that
+ /// once some mods use it, then all the other mods have to as well.
///
public abstract class KOSManagedWindow : MonoBehaviour
{
@@ -222,14 +231,13 @@ public bool IsOpen
}
///
- /// Pass in the absolute GUI screen location of a mouse click to decide whether
- /// or not this widget gets keyboard focus because of that click.
- /// (Clicking outside the window takes focus away. Clicking inside
- /// the window gives focus to the window and brings it to the front.)
- ///
+ /// Pass in the absolute GUI screen location of the mouse to decide whether
+ /// or not this window gets keyboard focus because of that position.
+ /// (If you want focus-follows-mouse logic, call this every Update(). If you
+ /// want click-to-focus logic, only call this in Update()s where a click just happened.)
/// Absolute position of mouse on whole screen
/// True if the window got focused, false if it didn't.
- public bool FocusClickLocationCheck(Vector2 absMousePos)
+ public bool FocusMouseLocationCheck(Vector2 absMousePos)
{
bool wasInside = false;
if (IsInsideMyExposedPortion(absMousePos))
@@ -286,18 +294,17 @@ public bool IsInsideMyExposedPortion(Vector2 posAbsolute)
///
/// When you subclass KOSManagedWindow, make sure that you call this
- /// from inside your Update. It does not use OnGUI because of the fact
- /// that the OnGUI event handler is broken - it only sends MouseDown
- /// and MouseUp events when the mouse is OUTSIDE the window, which is
- /// utterly backward, and it's hard to work out how to fix this,
- /// given how badly documented the Unity GUI API is. If anyone who
- /// actually understands the Unity GUI system wants to fix this,
- /// please feel free to do so.
+ /// from inside your Update() to check for focus change on the window.
+ /// Calling this will maybe call GetFocus() or LoseFocus() depending on
+ /// what the mouse is doing.
+ /// Note, you call this during *Update()*, NOT the OnGUI() call.
+ /// It does not use OnGUI() because the raw mousebutton state it
+ /// needs to see can get consumed and wiped by Unity's IMGUI widgets
+ /// before application code like this can see it.
///
- /// True if there was a mouseclick within this window.
- public bool UpdateLogic()
+ public void UpdateLogic()
{
- if (!IsOpen) return false;
+ if (!IsOpen) return;
// Input.mousePosition, unlike Event.current.MousePosition, puts the origin at the
// lower-left instead of upper-left of the screen, thus the subtraction in the y coord below:
@@ -305,23 +312,37 @@ public bool UpdateLogic()
// Mouse coord within the window, rather than within the screen.
mousePosRelative = new Vector2( mousePosAbsolute.x - windowRect.xMin, mousePosAbsolute.y - windowRect.yMin);
-
- bool clickUp = false;
+
+ // Could maybe cache the CustomParams call once up front to get a reference to the CTB instance, then only
+ // repeat the ".focusFollowsclick" part each update. The reason that's not being done here is that I
+ // noticed ClickThroughBlocker's OWN code always does it like this, and for all I know there might be
+ // an important reason. It always gets this value by using the fully qualified long chain you see
+ // here, starting from HighLogic, each update. :
+ bool clickToFocus = HighLogic.CurrentGame.Parameters.CustomParams().focusFollowsclick;
+
if (Input.GetMouseButtonDown(0))
{
mouseButtonDownPosAbsolute = mousePosAbsolute;
mouseButtonDownPosRelative = mousePosRelative;
}
-
- if (Input.GetMouseButtonUp(0))
+
+ bool mousePositionCanSetFocus = !(clickToFocus); // Always true in focus-follows-mouse mode
+
+ if (clickToFocus)
{
- clickUp = true;
- if (Vector2.Distance(mousePosAbsolute,mouseButtonDownPosAbsolute) <= dragTolerance)
+ if (Input.GetMouseButtonUp(0))
{
- FocusClickLocationCheck(mousePosAbsolute);
+ if (Vector2.Distance(mousePosAbsolute, mouseButtonDownPosAbsolute) <= dragTolerance)
+ {
+ mousePositionCanSetFocus = true;
+ }
}
}
- return IsInsideMyExposedPortion(mousePosAbsolute) && clickUp;
+
+ if (mousePositionCanSetFocus)
+ {
+ FocusMouseLocationCheck(mousePosAbsolute);
+ }
}
}
}
diff --git a/src/kOS/Screen/KOSNameTagWindow.cs b/src/kOS/Screen/KOSNameTagWindow.cs
index 2a6b462c27..4dc414b5da 100644
--- a/src/kOS/Screen/KOSNameTagWindow.cs
+++ b/src/kOS/Screen/KOSNameTagWindow.cs
@@ -1,6 +1,7 @@
-using kOS.Utilities;
+using kOS.Utilities;
using UnityEngine;
using kOS.Module;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
using System;
namespace kOS.Screen
@@ -124,7 +125,7 @@ public void OnGUI()
EditorLogic.fetch.Lock(false, false, false, "KOSNameTagLock");
GUI.skin = HighLogic.Skin;
- GUILayout.Window(myWindowId, windowRect, DrawWindow,"KOS nametag");
+ ClickThruBlocker.GUILayoutWindow(myWindowId, windowRect, DrawWindow,"KOS nametag");
// Ensure that the first time the window is made, it gets keybaord focus,
// but allow the focus to leave the window after that:
diff --git a/src/kOS/Screen/KOSTextEditPopup.cs b/src/kOS/Screen/KOSTextEditPopup.cs
index c5dfd823e6..f351a94c02 100644
--- a/src/kOS/Screen/KOSTextEditPopup.cs
+++ b/src/kOS/Screen/KOSTextEditPopup.cs
@@ -6,6 +6,7 @@
using kOS.Safe.Exceptions;
using kOS.Safe.Utilities;
using kOS.Module;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
namespace kOS.Screen
{
@@ -170,7 +171,7 @@ public void OnGUI()
CalcOuterCoords(); // force windowRect to lock to bottom edge of the parents
CalcInnerCoords();
- WindowRect = GUI.Window(UniqueId, WindowRect, ProcessWindow, "");
+ WindowRect = ClickThruBlocker.GUIWindow(UniqueId, WindowRect, ProcessWindow, "");
// Some mouse global state data used by several of the checks:
if (consumeEvent)
diff --git a/src/kOS/Screen/KOSToolbarWindow.cs b/src/kOS/Screen/KOSToolbarWindow.cs
index ce18c7f4cc..e5e461db9c 100644
--- a/src/kOS/Screen/KOSToolbarWindow.cs
+++ b/src/kOS/Screen/KOSToolbarWindow.cs
@@ -10,6 +10,8 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
+
namespace kOS.Screen
{
@@ -443,7 +445,7 @@ public void OnGUI()
GUI.skin = HighLogic.Skin;
- windowRect = GUILayout.Window(UNIQUE_ID, windowRect, DrawWindow, "kOS " + versionString);
+ windowRect = ClickThruBlocker.GUILayoutWindow(UNIQUE_ID, windowRect, DrawWindow, "kOS " + versionString);
windowRect = RectExtensions.ClampToRectAngle(windowRect, rectToFit);
}
diff --git a/src/kOS/Screen/ListPickerDialog.cs b/src/kOS/Screen/ListPickerDialog.cs
index 483c3d3c18..c902c13740 100644
--- a/src/kOS/Screen/ListPickerDialog.cs
+++ b/src/kOS/Screen/ListPickerDialog.cs
@@ -1,5 +1,7 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using UnityEngine;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
+
namespace kOS.Screen
{
@@ -110,7 +112,7 @@ public void OnGUI()
// Make sure it shifts enough to the left to fit the biggest string:
outerWindowRect.x = Mathf.Min(outerWindowRect.x, UnityEngine.Screen.width - outerWindowRect.width - 60);
- outerWindowRect = GUILayout.Window(
+ outerWindowRect = ClickThruBlocker.GUILayoutWindow(
title.GetHashCode(),
outerWindowRect,
DrawInnards,
diff --git a/src/kOS/Screen/TermWindow.cs b/src/kOS/Screen/TermWindow.cs
index b9eba082f7..a15bb85202 100644
--- a/src/kOS/Screen/TermWindow.cs
+++ b/src/kOS/Screen/TermWindow.cs
@@ -10,6 +10,7 @@
using kOS.Safe.UserIO;
using KSP.UI.Dialogs;
using kOS.Safe.Utilities;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
namespace kOS.Screen
{
@@ -360,7 +361,7 @@ void OnGUI()
// Should probably make "gui screen name for my CPU part" into some sort of utility method:
ChangeTitle(CalcualteTitle());
- WindowRect = GUI.Window(UniqueId, WindowRect, TerminalGui, TitleText);
+ WindowRect = ClickThruBlocker.GUIWindow(UniqueId, WindowRect, TerminalGui, TitleText);
if (consumeEvent)
{
diff --git a/src/kOS/UserIO/TelnetMainServer.cs b/src/kOS/UserIO/TelnetMainServer.cs
index f8c9919b3c..3759201f9a 100644
--- a/src/kOS/UserIO/TelnetMainServer.cs
+++ b/src/kOS/UserIO/TelnetMainServer.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Collections.Generic;
@@ -8,6 +8,8 @@
using kOS.Suffixed;
using UnityEngine;
using Debug = UnityEngine.Debug;
+using ClickThroughFix; // Needs ClickThroughBlocker DLL to be in the Reference directory.
+
namespace kOS.UserIO
{
@@ -371,10 +373,10 @@ public bool SetBindAddrFromString(string s)
void OnGUI()
{
if (activeOptInDialog)
- optInRect = GUILayout.Window(401123, // any made up number unlikely to clash is okay here
+ optInRect = ClickThruBlocker.GUILayoutWindow(401123, // any made up number unlikely to clash is okay here
optInRect, OptInOnGui, "kOS Telnet Opt-In Permisssion");
if (activeRealIPDialog)
- realIPRect = GUILayout.Window(401124, // any made up number unlikely to clash is okay here
+ realIPRect = ClickThruBlocker.GUILayoutWindow(401124, // any made up number unlikely to clash is okay here
realIPRect, RealIPOnGui, "kOS Telnet Non-Loopback Permisssion");
if (activeBgSimDialog)
bgSimRect = GUILayout.Window(401125, // any made up number unlikely to clash is okay here
diff --git a/src/kOS/kOS.csproj b/src/kOS/kOS.csproj
index f7f8495605..4a7d441a02 100644
--- a/src/kOS/kOS.csproj
+++ b/src/kOS/kOS.csproj
@@ -37,6 +37,10 @@
..\..\Resources\Assembly-CSharp-firstpass.dll
+
+ False
+ ..\..\Resources\ClickThroughBlocker.dll
+
..\..\Resources\GameData\kOS\Plugins\ICSharpCode.SharpZipLib.dll