Decompiled source of ValheimCursorLockFix v1.0.7

plugins\ValheimCursorLockFix.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ValheimCursorLockFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.7.0")]
[assembly: AssemblyInformationalVersion("1.0.7")]
[assembly: AssemblyProduct("ValheimCursorLockFix")]
[assembly: AssemblyTitle("ValheimCursorLockFix")]
[assembly: AssemblyVersion("1.0.7.0")]
namespace ValheimCursorLockFix;

[BepInPlugin("ua.gyrocopter.valheim.cursorlockfix", "ValheimCursorLockFix", "1.0.7")]
[DefaultExecutionOrder(10000)]
public sealed class ValheimCursorLockFixPlugin : BaseUnityPlugin
{
	private struct RECT
	{
		public int Left;

		public int Top;

		public int Right;

		public int Bottom;
	}

	private struct POINT
	{
		public int X;

		public int Y;
	}

	public const string PluginGuid = "ua.gyrocopter.valheim.cursorlockfix";

	public const string PluginName = "ValheimCursorLockFix";

	public const string PluginVersion = "1.0.7";

	private const float ChatStickySeconds = 1f;

	private ConfigEntry<bool> _enableFix;

	private ConfigEntry<bool> _forceLockInGameplay;

	private ConfigEntry<bool> _unlockInMenus;

	private ConfigEntry<bool> _hardConfine;

	private ConfigEntry<bool> _centerWhenHidden;

	private ConfigEntry<int> _edgePadding;

	private ConfigEntry<int> _pollIntervalMs;

	private ConfigEntry<KeyboardShortcut> _toggleKey;

	private ConfigEntry<bool> _debugLogging;

	private bool _lastShouldLock;

	private bool _hasAppliedCursorState;

	private string _lastReason;

	private float _chatStickyUntil;

	private IntPtr _windowHandle = IntPtr.Zero;

	private readonly object _stateLock = new object();

	private RECT _targetRect;

	private bool _targetValid;

	private bool _shouldCenter;

	private volatile bool _running;

	private Thread _enforcementThread;

	private bool _clipActive;

	private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();

	private void Awake()
	{
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		_enableFix = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableFix", true, "Enable or disable the cursor lock fix.");
		_forceLockInGameplay = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ForceLockInGameplay", true, "Force Unity cursor lock while gameplay is active.");
		_unlockInMenus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UnlockInMenus", true, "Force cursor unlock while menus / chat / map / etc. are active.");
		_hardConfine = ((BaseUnityPlugin)this).Config.Bind<bool>("HardConfine", "Enabled", true, "Use Win32 ClipCursor so the OS cursor cannot leave the game window.");
		_centerWhenHidden = ((BaseUnityPlugin)this).Config.Bind<bool>("HardConfine", "CenterWhenHidden", true, "While the cursor is hidden in gameplay, pin it to the center of the game window.");
		_edgePadding = ((BaseUnityPlugin)this).Config.Bind<int>("HardConfine", "EdgePadding", 0, "Shrink the clip rectangle by this many pixels on each side.");
		_pollIntervalMs = ((BaseUnityPlugin)this).Config.Bind<int>("HardConfine", "PollIntervalMs", 4, "How often the background thread re-asserts ClipCursor (1-100 ms).");
		_toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Keyboard shortcut that toggles EnableFix during play.");
		_debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Enable verbose debug logging.");
		_running = true;
		_enforcementThread = new Thread(EnforcementLoop)
		{
			IsBackground = true,
			Name = "ValheimCursorLockFix.Clip",
			Priority = ThreadPriority.AboveNormal
		};
		_enforcementThread.Start();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"ValheimCursorLockFix 1.0.7 loaded.");
	}

	private void Update()
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		KeyboardShortcut value = _toggleKey.Value;
		if (((KeyboardShortcut)(ref value)).IsDown())
		{
			_enableFix.Value = !_enableFix.Value;
			((BaseUnityPlugin)this).Config.Save();
			DebugLog("EnableFix toggled to " + _enableFix.Value + ".");
			if (!_enableFix.Value)
			{
				ApplyUnlock("fix disabled");
				PublishClip(valid: false, center: false, default(RECT));
			}
		}
	}

	private void LateUpdate()
	{
		object chatInput;
		string reason;
		if (!_enableFix.Value)
		{
			PublishClip(valid: false, center: false, default(RECT));
		}
		else if (TryGetChatSession(out chatInput))
		{
			ApplyChatCursorMode("chat");
			PublishHardConfine(center: true);
			RestoreChatFocus(chatInput);
		}
		else if (ShouldLockCursor(out reason))
		{
			if (_forceLockInGameplay.Value)
			{
				ApplyLock(reason);
			}
			PublishHardConfine(center: true);
		}
		else
		{
			if (_unlockInMenus.Value)
			{
				ApplyUnlock(reason);
			}
			PublishClip(valid: false, center: false, default(RECT));
		}
	}

	private void PublishHardConfine(bool center)
	{
		if (!_hardConfine.Value || !Application.isFocused)
		{
			PublishClip(valid: false, center: false, default(RECT));
			return;
		}
		IntPtr intPtr = ResolveWindowHandle();
		if (intPtr == IntPtr.Zero || GetForegroundWindow() != intPtr)
		{
			PublishClip(valid: false, center: false, default(RECT));
		}
		else
		{
			if (!GetClientRect(intPtr, out var lpRect))
			{
				return;
			}
			POINT lpPoint = new POINT
			{
				X = lpRect.Left,
				Y = lpRect.Top
			};
			POINT lpPoint2 = new POINT
			{
				X = lpRect.Right,
				Y = lpRect.Bottom
			};
			if (ClientToScreen(intPtr, ref lpPoint) && ClientToScreen(intPtr, ref lpPoint2))
			{
				int num = Mathf.Max(0, _edgePadding.Value);
				RECT rect = new RECT
				{
					Left = lpPoint.X + num,
					Top = lpPoint.Y + num,
					Right = lpPoint2.X - num,
					Bottom = lpPoint2.Y - num
				};
				if (rect.Right > rect.Left && rect.Bottom > rect.Top)
				{
					bool center2 = center && _centerWhenHidden.Value;
					PublishClip(valid: true, center2, rect);
				}
			}
		}
	}

	private void PublishClip(bool valid, bool center, RECT rect)
	{
		lock (_stateLock)
		{
			_targetValid = valid;
			_shouldCenter = center;
			_targetRect = rect;
		}
	}

	private void EnforcementLoop()
	{
		while (_running)
		{
			bool targetValid;
			bool shouldCenter;
			RECT lpRect;
			lock (_stateLock)
			{
				targetValid = _targetValid;
				shouldCenter = _shouldCenter;
				lpRect = _targetRect;
			}
			int num = 4;
			try
			{
				if (targetValid)
				{
					ClipCursor(ref lpRect);
					_clipActive = true;
					POINT lpPoint;
					if (shouldCenter)
					{
						SetCursorPos((lpRect.Left + lpRect.Right) / 2, (lpRect.Top + lpRect.Bottom) / 2);
					}
					else if (GetCursorPos(out lpPoint) && !InsideRect(lpRect, lpPoint))
					{
						SetCursorPos(Clamp(lpPoint.X, lpRect.Left, lpRect.Right - 1), Clamp(lpPoint.Y, lpRect.Top, lpRect.Bottom - 1));
					}
				}
				else if (_clipActive)
				{
					ClipCursor(IntPtr.Zero);
					_clipActive = false;
				}
				num = ((_pollIntervalMs == null) ? 4 : Mathf.Clamp(_pollIntervalMs.Value, 1, 100));
			}
			catch
			{
			}
			Thread.Sleep(targetValid ? num : 32);
		}
		if (_clipActive)
		{
			ClipCursor(IntPtr.Zero);
			_clipActive = false;
		}
	}

	private bool ShouldLockCursor(out string reason)
	{
		if (!Application.isFocused)
		{
			reason = "application not focused";
			return false;
		}
		if (!HasZInputInstance())
		{
			reason = "ZInput unavailable";
			return false;
		}
		if (!HasLocalPlayer())
		{
			reason = "local player unavailable";
			return false;
		}
		if (InvokeStaticBool(FindType("Hud"), "InRadial"))
		{
			reason = "radial menu";
			return false;
		}
		if (InvokeStaticBool(FindType("Menu"), "IsActive"))
		{
			reason = "menu active";
			return false;
		}
		if (InvokeStaticBool(FindType("InventoryGui"), "IsVisible"))
		{
			reason = "inventory visible";
			return false;
		}
		if (InvokeStaticBool(FindType("Hud"), "IsPieceSelectionVisible"))
		{
			reason = "build menu visible";
			return false;
		}
		if (InvokeStaticBool(FindType("Minimap"), "IsOpen"))
		{
			reason = "map open";
			return false;
		}
		if (InvokeStaticBool(FindType("StoreGui"), "IsVisible"))
		{
			reason = "store visible";
			return false;
		}
		if (InvokeStaticBool(FindType("TextInput"), "IsVisible"))
		{
			reason = "text input visible";
			return false;
		}
		if (IsTextViewerVisibleSafe())
		{
			reason = "text viewer visible";
			return false;
		}
		if (InvokeStaticBool(FindType("Console"), "IsVisible"))
		{
			reason = "console visible";
			return false;
		}
		if (InvokeStaticBool(FindType("UnifiedPopup"), "IsVisible"))
		{
			reason = "popup visible";
			return false;
		}
		if (IsPasswordDialogShowingSafe())
		{
			reason = "password dialog";
			return false;
		}
		reason = "gameplay";
		return true;
	}

	private void ApplyChatCursorMode(string reason)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		TrySetZCursorLock((CursorLockMode)0);
		if ((int)Cursor.lockState != 0)
		{
			Cursor.lockState = (CursorLockMode)0;
		}
		TryZCursorHide();
		if (Cursor.visible)
		{
			Cursor.visible = false;
		}
		LogCursorStateChanged(shouldLock: true, reason);
	}

	private void ApplyLock(string reason)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Invalid comparison between Unknown and I4
		TrySetZCursorLock((CursorLockMode)1);
		if ((int)Cursor.lockState != 1)
		{
			Cursor.lockState = (CursorLockMode)1;
		}
		TryZCursorHide();
		if (Cursor.visible)
		{
			Cursor.visible = false;
		}
		LogCursorStateChanged(shouldLock: true, reason);
	}

	private void ApplyUnlock(string reason)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		TrySetZCursorLock((CursorLockMode)0);
		if ((int)Cursor.lockState != 0)
		{
			Cursor.lockState = (CursorLockMode)0;
		}
		TryZCursorShow();
		if (!Cursor.visible)
		{
			Cursor.visible = true;
		}
		LogCursorStateChanged(shouldLock: false, reason);
	}

	private void LogCursorStateChanged(bool shouldLock, string reason)
	{
		if (!_hasAppliedCursorState || _lastShouldLock != shouldLock || !string.Equals(_lastReason, reason, StringComparison.Ordinal))
		{
			_hasAppliedCursorState = true;
			_lastShouldLock = shouldLock;
			_lastReason = reason;
			DebugLog((shouldLock ? "Locked" : "Unlocked") + " cursor: " + reason + ".");
		}
	}

	private bool HasZInputInstance()
	{
		return GetStaticMemberValue(FindType("ZInput"), "instance") != null;
	}

	private bool HasLocalPlayer()
	{
		return GetStaticMemberValue(FindType("Player"), "m_localPlayer") != null;
	}

	private bool IsTextViewerVisibleSafe()
	{
		return InvokeInstanceBool(GetStaticMemberValue(FindType("TextViewer"), "instance"), "IsVisible");
	}

	private bool IsPasswordDialogShowingSafe()
	{
		object staticMemberValue = GetStaticMemberValue(FindType("ZNet"), "instance");
		if (!InvokeInstanceBool(staticMemberValue, "IsPasswordDialogShowing"))
		{
			return InvokeInstanceBool(staticMemberValue, "InPasswordDialog");
		}
		return true;
	}

	private bool TryGetChatSession(out object chatInput)
	{
		chatInput = null;
		object staticMemberValue = GetStaticMemberValue(FindType("Chat"), "instance");
		if (staticMemberValue == null)
		{
			return Time.unscaledTime < _chatStickyUntil;
		}
		chatInput = GetInstanceMemberValue(staticMemberValue, "m_input");
		bool num = InvokeInstanceBool(staticMemberValue, "HasFocus");
		bool result = default(bool);
		bool flag = chatInput != null && TryGetBoolProperty(chatInput, "isFocused", out result) && result;
		bool flag2 = IsUnityObjectActive(chatInput);
		bool instanceBoolField = GetInstanceBoolField(staticMemberValue, "m_wasFocused");
		if (num || flag)
		{
			_chatStickyUntil = Time.unscaledTime + 1f;
			return true;
		}
		if (flag2 && (instanceBoolField || Time.unscaledTime < _chatStickyUntil))
		{
			_chatStickyUntil = Time.unscaledTime + 1f;
			return true;
		}
		return false;
	}

	private void RestoreChatFocus(object chatInput)
	{
		if (chatInput == null || !IsUnityObjectActive(chatInput) || (TryGetBoolProperty(chatInput, "isFocused", out var result) && result))
		{
			return;
		}
		try
		{
			Type type = chatInput.GetType();
			MethodInfo method = type.GetMethod("ActivateInputField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			MethodInfo? method2 = type.GetMethod("Select", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			method?.Invoke(chatInput, null);
			method2?.Invoke(chatInput, null);
			PropertyInfo property = type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			int num = ((property != null) ? (property.GetValue(chatInput, null) as string) : null)?.Length ?? 0;
			PropertyInfo property2 = type.GetProperty("caretPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			PropertyInfo property3 = type.GetProperty("selectionAnchorPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			PropertyInfo? property4 = type.GetProperty("selectionFocusPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			property2?.SetValue(chatInput, num, null);
			property3?.SetValue(chatInput, num, null);
			property4?.SetValue(chatInput, num, null);
			DebugLog("Restored chat input focus after blip.");
		}
		catch
		{
		}
	}

	private void OnApplicationFocus(bool hasFocus)
	{
		if (!hasFocus)
		{
			_windowHandle = IntPtr.Zero;
			PublishClip(valid: false, center: false, default(RECT));
		}
	}

	private void OnDestroy()
	{
		Shutdown();
	}

	private void OnApplicationQuit()
	{
		Shutdown();
	}

	private void Shutdown()
	{
		PublishClip(valid: false, center: false, default(RECT));
		_running = false;
		if (_enforcementThread != null)
		{
			_enforcementThread.Join(250);
			_enforcementThread = null;
		}
		ClipCursor(IntPtr.Zero);
		_clipActive = false;
	}

	private IntPtr ResolveWindowHandle()
	{
		if (_windowHandle != IntPtr.Zero && IsWindow(_windowHandle))
		{
			return _windowHandle;
		}
		IntPtr activeWindow = GetActiveWindow();
		_windowHandle = ((activeWindow != IntPtr.Zero && IsWindow(activeWindow)) ? activeWindow : IntPtr.Zero);
		return _windowHandle;
	}

	private static bool InsideRect(RECT r, POINT p)
	{
		if (p.X >= r.Left && p.X < r.Right && p.Y >= r.Top)
		{
			return p.Y < r.Bottom;
		}
		return false;
	}

	private static int Clamp(int value, int min, int max)
	{
		if (max < min)
		{
			return min;
		}
		if (value >= min)
		{
			if (value <= max)
			{
				return value;
			}
			return max;
		}
		return min;
	}

	private static bool TrySetZCursorLock(CursorLockMode mode)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		Type type = FindType("ZCursor");
		if (type == null)
		{
			return false;
		}
		try
		{
			PropertyInfo property = type.GetProperty("LockState", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (property == null)
			{
				return false;
			}
			property.SetValue(null, mode, null);
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static bool TryZCursorHide()
	{
		Type type = FindType("ZCursor");
		if (type == null)
		{
			return false;
		}
		try
		{
			MethodInfo method = type.GetMethod("Hide", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null)
			{
				return false;
			}
			method.Invoke(null, null);
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static bool TryZCursorShow()
	{
		Type type = FindType("ZCursor");
		if (type == null)
		{
			return false;
		}
		try
		{
			MethodInfo method = type.GetMethod("Show", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null)
			{
				return false;
			}
			method.Invoke(null, null);
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static Type FindType(string typeName)
	{
		if (TypeCache.TryGetValue(typeName, out var value))
		{
			return value;
		}
		Type type = Type.GetType(typeName);
		if (type != null)
		{
			TypeCache[typeName] = type;
			return type;
		}
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		for (int i = 0; i < assemblies.Length; i++)
		{
			Type type2 = assemblies[i].GetType(typeName);
			if (type2 != null)
			{
				TypeCache[typeName] = type2;
				return type2;
			}
		}
		TypeCache[typeName] = null;
		return null;
	}

	private static object GetStaticMemberValue(Type type, string memberName)
	{
		if (type == null)
		{
			return null;
		}
		try
		{
			FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(null);
			}
			PropertyInfo property = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			return (property != null) ? property.GetValue(null, null) : null;
		}
		catch
		{
			return null;
		}
	}

	private static object GetInstanceMemberValue(object instance, string memberName)
	{
		if (instance == null)
		{
			return null;
		}
		try
		{
			Type type = instance.GetType();
			FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return field.GetValue(instance);
			}
			PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			return (property != null) ? property.GetValue(instance, null) : null;
		}
		catch
		{
			return null;
		}
	}

	private static bool GetInstanceBoolField(object instance, string memberName)
	{
		object instanceMemberValue = GetInstanceMemberValue(instance, memberName);
		bool flag = default(bool);
		int num;
		if (instanceMemberValue is bool)
		{
			flag = (bool)instanceMemberValue;
			num = 1;
		}
		else
		{
			num = 0;
		}
		return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
	}

	private static bool IsUnityObjectActive(object value)
	{
		if (value == null)
		{
			return false;
		}
		if (TryGetBoolProperty(value, "activeInHierarchy", out var result) || TryGetBoolProperty(value, "activeSelf", out result) || TryGetNestedGameObjectActive(value, out result))
		{
			return result;
		}
		return false;
	}

	private static bool TryGetNestedGameObjectActive(object value, out bool active)
	{
		active = false;
		try
		{
			PropertyInfo property = value.GetType().GetProperty("gameObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			object value2 = ((property != null) ? property.GetValue(value, null) : null);
			return TryGetBoolProperty(value2, "activeInHierarchy", out active) || TryGetBoolProperty(value2, "activeSelf", out active);
		}
		catch
		{
			return false;
		}
	}

	private static bool TryGetBoolProperty(object value, string propertyName, out bool result)
	{
		result = false;
		if (value == null)
		{
			return false;
		}
		try
		{
			PropertyInfo property = value.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property == null || property.PropertyType != typeof(bool))
			{
				return false;
			}
			result = (bool)property.GetValue(value, null);
			return true;
		}
		catch
		{
			return false;
		}
	}

	private static bool InvokeStaticBool(Type type, string methodName)
	{
		if (type == null)
		{
			return false;
		}
		MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
		if (method == null || method.ReturnType != typeof(bool))
		{
			return false;
		}
		try
		{
			return (bool)method.Invoke(null, null);
		}
		catch
		{
			return false;
		}
	}

	private static bool InvokeInstanceBool(object instance, string methodName)
	{
		if (instance == null)
		{
			return false;
		}
		MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
		if (method == null || method.ReturnType != typeof(bool))
		{
			return false;
		}
		try
		{
			return (bool)method.Invoke(instance, null);
		}
		catch
		{
			return false;
		}
	}

	private void DebugLog(string message)
	{
		if (_debugLogging != null && _debugLogging.Value)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
		}
	}

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool ClipCursor(ref RECT lpRect);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool ClipCursor(IntPtr lpRect);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool SetCursorPos(int x, int y);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool GetCursorPos(out POINT lpPoint);

	[DllImport("user32.dll")]
	private static extern IntPtr GetForegroundWindow();

	[DllImport("user32.dll")]
	private static extern IntPtr GetActiveWindow();

	[DllImport("user32.dll")]
	private static extern bool IsWindow(IntPtr hWnd);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);

	[DllImport("user32.dll", SetLastError = true)]
	private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);
}