Spring – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Gma.System.MouseKeyHook;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DirectWrite;
using SharpDX.Mathematics.Interop;
using Spring.Utilities;
using FontFactory = SharpDX.DirectWrite.Factory;
using Point = System.Drawing.Point;
using TextAntialiasMode = SharpDX.Direct2D1.TextAntialiasMode;

namespace Spring.HUD
{
    public sealed class OSD : DirectXOverlay
    {
#region Public Enums, Properties and Fields

        public new string Text { get; set; }

        public Configuration.Configuration Configuration { get; }

        public RawColor4 SharpColor { get; set; }

        public static List<Regex> DirectXRegex { get; set; }

#endregion

#region Private Delegates, Events, Enums, Properties, Indexers and Fields

        private static ConcurrentDictionary<uint, bool> DirectRenderWindow { get; set; }

        private IKeyboardMouseEvents KeyboardMouseEvents { get; }

        private HashSet<IntPtr> DesktopWindow { get; }

        private FontFactory FactoryWrite { get; }

        private TextFormat TextFormat { get; }

        private RectangleF TextRectangle { get; set; }

#endregion

#region Constructors, Destructors and Finalizers

        public OSD(Configuration.Configuration springConfiguration, IKeyboardMouseEvents keyboardMouseEvents) : this()
        {
            DirectXRegex = new List<Regex>();

            Configuration = springConfiguration;

            foreach (var expression in Configuration.HUD.WhiteList.Whitelist)
            {
                try
                {
                    var regex = new Regex(expression, RegexOptions.Compiled | RegexOptions.IgnoreCase);
                    DirectXRegex.Add(regex);
                }
                catch
                {
                    // Skip 
                }
            }

            var color = Configuration.HUD.Color;

            SharpColor = new RawColor4(((float) color.R).MapValueToRange(0, 255, 0, 1),
                ((float) color.G).MapValueToRange(0, 255, 0, 1),
                ((float) color.B).MapValueToRange(0, 255, 0, 1),
                ((float) color.A).MapValueToRange(0, 255, 0, 1));

            KeyboardMouseEvents = keyboardMouseEvents;

            Configuration.HUD.PropertyChanged += HUD_PropertyChanged;
            KeyboardMouseEvents.MouseClick += KeyboardMouseEvents_MouseClick;
            KeyboardMouseEvents.MouseDragStarted += KeyboardMouseEvents_MouseDragStarted;
            KeyboardMouseEvents.MouseDragFinished += KeyboardMouseEvents_MouseDragFinished;
        }

        private OSD()
        {
            FactoryWrite = new FontFactory();
            TextFormat = new TextFormat(FactoryWrite, "Courier New", 36);
            DesktopWindow = new HashSet<IntPtr>(GetDesktopWindow());
            DirectRenderWindow = new ConcurrentDictionary<uint, bool>();

            Start(TimeSpan.FromSeconds(1));
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Dispose stuff here
                Configuration.HUD.PropertyChanged -= HUD_PropertyChanged;
                KeyboardMouseEvents.MouseClick -= KeyboardMouseEvents_MouseClick;
                KeyboardMouseEvents.MouseDragStarted -= KeyboardMouseEvents_MouseDragStarted;
                KeyboardMouseEvents.MouseDragFinished -= KeyboardMouseEvents_MouseDragFinished;

                Stop(TimeSpan.FromSeconds(1));
            }

            base.Dispose(disposing);
        }

#endregion

#region Event Handlers

        private void HUD_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Configuration.HUD = (Configuration.HUD) sender;

            var color = Configuration.HUD.Color;

            SharpColor = new RawColor4(((float) color.R).MapValueToRange(0, 255, 0, 1),
                ((float) color.G).MapValueToRange(0, 255, 0, 1),
                ((float) color.B).MapValueToRange(0, 255, 0, 1),
                ((float) color.A).MapValueToRange(0, 255, 0, 1));

            RedrawWindow();

            switch (Configuration.HUD.Enable)
            {
                case true:
                    Start(TimeSpan.FromSeconds(1));

                    break;

                default:
                    Stop(TimeSpan.FromSeconds(1));

                    break;
            }
        }

        private void KeyboardMouseEvents_MouseDragFinished(object sender, MouseEventArgs e)
        {
            RedrawWindow();
        }

        private void KeyboardMouseEvents_MouseDragStarted(object sender, MouseEventArgs e)
        {
            Visible = false;
        }

        private void KeyboardMouseEvents_MouseClick(object sender, MouseEventArgs e)
        {
            RedrawWindow();
        }

#endregion

#region Private Methods

        private void RedrawWindow()
        {
            var fg = GetForegroundWindow();

            if (Configuration.HUD.EnableWhiteList)
            {
                if (!IsDirectRenderWindow(fg))
                {
                    Visible = false;

                    return;
                }
            }

            if (DesktopWindow.Contains(fg))
            {
                Visible = false;

                return;
            }

            OverlayWindow(fg);

            Visible = true;
        }

        private static bool IsDirectRenderWindow(IntPtr fg)
        {
            OSDNative.GetWindowThreadProcessId(fg, out var pid);

            if (DirectRenderWindow.TryGetValue(pid, out var directRender))
            {
                return directRender;
            }

            var process = Process.GetProcessById((int) pid);

            $"Module count: {process.Modules.Count}".ToDebugConsole();

            var expressions = new Regex[DirectXRegex.Count];
            DirectXRegex.CopyTo(expressions);

            var regexList = new List<Regex>(expressions);

            foreach (var module in process.Modules.OfType<ProcessModule>())
            {
                $"Module: {process.Modules.Count}".ToDebugConsole();

                foreach (var regex in expressions)
                {
                    if (regex.IsMatch(module.ModuleName))
                    {
                        regexList.Remove(regex);

                        if (regexList.Count == 0)
                        {
                            break;
                        }
                    }
                }
            }

            DirectRenderWindow.TryAdd(pid, regexList.Count == 0);

            return regexList.Count == 0;
        }

        private static IEnumerable<IntPtr> GetDesktopWindow()
        {
            var programManager = OSDNative.GetShellWindow();
            var shellDllDefViewParent = programManager;
            var shellDllDefView = OSDNative.FindWindowEx(programManager, IntPtr.Zero, "SHELLDLL_DefView", null);

            var sysListView32 =
                OSDNative.FindWindowEx(shellDllDefView, IntPtr.Zero, "SysListView32", "FolderView");

            if (shellDllDefView == IntPtr.Zero)
            {
                OSDNative.EnumWindows((hWnd, lParam) =>
                        {
                            var sb = new StringBuilder();
                            OSDNative.GetClassName(hWnd, sb, 8);

                            if (sb.ToString() != "WorkerW")
                            {
                                return true;
                            }

                            var child = OSDNative.FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null);

                            if (child == IntPtr.Zero)
                            {
                                return true;
                            }

                            shellDllDefViewParent = hWnd;
                            shellDllDefView = child;

                            sysListView32 =
                                OSDNative.FindWindowEx(child, IntPtr.Zero, "SysListView32", "FolderView");

                            return false;
                        },
                    0);
            }

            return new[] { programManager, shellDllDefViewParent, shellDllDefView, sysListView32 };
        }

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

        private protected override void SDxRendering(WindowRenderTarget render)
        {
            if (Configuration == null || Configuration.HUD == null || !Configuration.HUD.Enable)
            {
                return;
            }

            if (render == null)
            {
                return;
            }

            render.TextAntialiasMode = TextAntialiasMode.Aliased;
            render.AntialiasMode = AntialiasMode.Aliased;
            render.BeginDraw();
            render.Clear(Color.Transparent);

            if (!string.IsNullOrEmpty(Text))
            {
                using (var textBrush = new SolidColorBrush(render, SharpColor))
                {
                    render.DrawText(Text, TextFormat, TextRectangle, textBrush);
                }
            }

            render.EndDraw();
        }

        private void OverlayWindow(IntPtr hWnd)
        {
            OSDNative.GetWindowRect(hWnd, out var fgRect);

            var w = fgRect.Right - fgRect.Left;
            var h = fgRect.Bottom - fgRect.Top;

            TextRectangle = new RectangleF(0, 0, w, 50);

            var x = (int) (Configuration.HUD.Position.X * w / 100f);
            var y = (int) (Configuration.HUD.Position.Y * h / 100f);

            var point = new Point(x, y);
            OSDNative.ClientToScreen(hWnd, ref point);

            Left = point.X;
            Top = (int) (point.Y - TextRectangle.Height);
            Width = w;
            Height = h;
        }

#endregion

#region Nested Types

        public class Symbols
        {
#region Static Fields and Constants

            public static Func<string> HUDShown = () => "☍";

            public static Func<string> KeyPressed = () => "(☍)";

            public static Func<string> KeyReleased = () => "☍";

            public static Func<string, string> KeyDown = arg => $"(☍)x{arg}";

            public static Func<string, string> DischargeProgress = arg => $"☍⤳{arg}";

            public static Func<string, string> DischargeStop = arg => $"☍x{arg}";

            public static Func<string> ChargeStart = () => "☍⬿";

            public static Func<string, string> ChargeStop = arg => $"☍x{arg}";

#endregion
        }

        internal class OSDNative
        {
#region Public Events & Delegates

            public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);

#endregion

#region Public Methods

            [DllImport("user32.dll", SetLastError = true)]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lPdwProcessId);

            [DllImport("user32.dll")]
            public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);

            [DllImport("user32.dll")]
            public static extern int GetClassName(IntPtr hWnd, StringBuilder buf, int nMaxCount);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool GetWindowRect(IntPtr hWnd, out DirectXOverlayNative.Rect lpRect);

            [DllImport("user32.DLL")]
            public static extern IntPtr GetShellWindow();

            [DllImport("user32.DLL")]
            public static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

            [DllImport("user32.dll", SetLastError = true)]
            public static extern IntPtr FindWindowEx(IntPtr hWndParent,
                                                     IntPtr hWndChildAfter,
                                                     string lpszClass,
                                                     string lpszWindow);

            [DllImport("user32.dll", SetLastError = true)]
            public static extern bool SetWindowPos(IntPtr hWnd,
                                                   IntPtr hWndInsertAfter,
                                                   int X,
                                                   int Y,
                                                   int cx,
                                                   int cy,
                                                   uint uFlags);

#endregion
        }

#endregion
    }
}