csharp/C1rdec/Poe-Lurker/src/Lurker/Helpers/DockingHelper.cs

DockingHelper.cs
//-----------------------------------------------------------------------
// 
//     Copyright © Wohs Inc.
// 
//-----------------------------------------------------------------------

namespace Lurker.Helpers
{
    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Threading.Tasks;
    using Lurker.Extensions;
    using Lurker.Models;
    using Lurker.Services;
    using static Lurker.Native;

    /// 
    /// Represents DockingHelper.
    /// 
    /// 
    public clast DockingHelper : IDisposable
    {
        #region Fields

        private const uint GainMouseCapture = 8;
        private const uint LostMouseCapture = 9;
        private const uint MoveEnd = 11;

        private static readonly int DefaultFlaskBarHeight = 122;
        private static readonly int DefaultFlaskBarWidth = 550;
        private static readonly int DefaultExpBarHeight = 24;
        private static readonly int DefaultHeight = 1080;

        private readonly uint _windowProcessId;
        private readonly uint _windowOwnerId;
        private readonly WinEventDelegate _winEventDelegate;

        private Process _myProcess;
        private CancellationTokenSource _tokenSource;
        private SettingsService _settingsService;
        private IntPtr _hook;
        private IntPtr _windowHandle;
        private IntPtr _currentWindowStyle;
        private Process _process;

        #endregion

        #region Constructors

        /// 
        /// Initializes a new instance of the  clast.
        /// 
        /// The process identifier.
        /// The settings service.
        public DockingHelper(int processId, SettingsService settingsService)
        {
            this._process = ProcessLurker.GetProcessById(processId);
            if (this._process != null)
            {
                this._myProcess = Process.GetCurrentProcess();
                this._tokenSource = new CancellationTokenSource();
                this._settingsService = settingsService;
                this._windowHandle = this._process.GetWindowHandle();

                this._windowOwnerId = GetWindowThreadProcessId(this._windowHandle, out this._windowProcessId);
                this._winEventDelegate = this.WhenWindowMoveStartsOrEnds;
                this._hook = SetWinEventHook(0, MoveEnd, this._windowHandle, this._winEventDelegate, this._windowProcessId, this._windowOwnerId, 0);
                this.WindowInformation = this.GetWindowInformation();
                this.WatchForegound();

                /*if (settingsService.VulkanRenderer)
                {
                    this.RemoveWindowBorder();
                }*/
            }
        }

        #endregion

        #region Events

        /// 
        /// Occurs when [on window move].
        /// 
        public event EventHandler OnWindowMove;

        /// 
        /// Occurs when [on lost mouse capture].
        /// 
        public event EventHandler OnLostMouseCapture;

        /// 
        /// Occurs when [on mouse capture].
        /// 
        public event EventHandler OnMouseCapture;

        /// 
        /// Occurs when [on foreground change].
        /// 
        public event EventHandler OnForegroundChange;

        #endregion

        #region Properties

        /// 
        /// Gets a value indicating whether this instance is window in foreground.
        /// 
        public bool IsWindowInForeground => this._windowHandle == Native.GetForegroundWindow();

        /// 
        /// Gets the window information.
        /// 
        public PoeWindowInformation WindowInformation { get; private set; }

        #endregion

        #region Methods

        /// 
        /// Performs application-defined tasks astociated with freeing, releasing, or resetting unmanaged resources.
        /// 
        public void Dispose()
        {
            this.Dispose(true);
        }

        /// 
        /// Sets the foreground.
        /// 
        public void SetForeground()
        {
            this.SetForeground(this._windowHandle);
        }

        /// 
        /// Sets the foreground.
        /// 
        /// The handle.
        public void SetForeground(IntPtr handle)
        {
            Native.SetForegroundWindow(handle);
        }

        /// 
        /// Releases unmanaged and - optionally - managed resources.
        /// 
        /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this._myProcess.Dispose();
                this._tokenSource.Cancel();
                UnhookWinEvent(this._hook);
            }
        }

        /// 
        /// Removes the window border.
        /// 
        private void RemoveWindowBorder()
        {
            // Native.SetWindowLongPtr(new HandleRef(this, this._windowHandle), -16, (IntPtr)0x10000000);
            Native.SetWindowLong(this._windowHandle, -16, 0x10000000);
        }

        /// 
        /// Whens the window move starts or ends.
        /// 
        /// The h win event hook.
        /// Type of the event.
        /// The HWND.
        /// The identifier object.
        /// The identifier child.
        /// The dw event thread.
        /// The DWMS event time.
        private void WhenWindowMoveStartsOrEnds(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
        {
            if (this._windowHandle != hwnd)
            {
                return;
            }

            switch (eventType)
            {
                case MoveEnd:
                    this.InvokeWindowMove();
                    break;
                case GainMouseCapture:
                    this.Invoke(this.OnMouseCapture);
                    break;
                case LostMouseCapture:
                    this.Invoke(this.OnLostMouseCapture);
                    break;
            }
        }

        /// 
        /// Watches the foregound.
        /// 
        private async void WatchForegound()
        {
            var token = this._tokenSource.Token;
            while (true)
            {
                try
                {
                    if (token.IsCancellationRequested)
                    {
                        return;
                    }

                    var inForeground = false;
                    var foregroundWindow = Native.GetForegroundWindow();
                    GetWindowThreadProcessId(foregroundWindow, out var processId);

                    var style = Native.GetWindowLong(this._windowHandle, -16);
                    if (this._currentWindowStyle != style)
                    {
                        switch ((uint)style)
                        {
                            case 0x14cf0000:
                                PoeApplicationContext.WindowStyle = WindowStyle.Windowed;
                                break;
                            case 0x94000000:
                                PoeApplicationContext.WindowStyle = WindowStyle.WindowedFullScreen;
                                break;
                        }

                        this._currentWindowStyle = style;
                        this.InvokeWindowMove();
                    }

                    if (processId == this._myProcess.Id || foregroundWindow == this._windowHandle)
                    {
                        inForeground = true;
                    }

                    if (PoeApplicationContext.InForeground != inForeground)
                    {
                        PoeApplicationContext.InForeground = inForeground;
                        if (this._settingsService.HideInBackground)
                        {
                            this.OnForegroundChange?.Invoke(this, inForeground);
                        }
                    }

                    await Task.Delay(500);
                }
                catch
                {
                }
            }
        }

        /// 
        /// Invokes the specified handler.
        /// 
        /// The handler.
        private void Invoke(EventHandler handler)
        {
            handler?.Invoke(this, EventArgs.Empty);
        }

        /// 
        /// Invokes the window move.
        /// 
        private void InvokeWindowMove()
        {
            this.WindowInformation = this.GetWindowInformation();
            this.OnWindowMove?.Invoke(this, this.WindowInformation);
        }

        /// 
        /// Gets the window information.
        /// 
        /// The poe window information.
        private PoeWindowInformation GetWindowInformation()
        {
            Rect poePosition = default;
            Native.GetWindowRect(this._windowHandle, ref poePosition);
            double poeWidth = poePosition.Right - poePosition.Left;
            double poeHeight = poePosition.Bottom - poePosition.Top;

            var expBarHeight = poeHeight * DefaultExpBarHeight / DefaultHeight;
            var flaskBarWidth = poeHeight * DefaultFlaskBarWidth / DefaultHeight;
            var flaskBarHeight = poeHeight * DefaultFlaskBarHeight / DefaultHeight;

            return new PoeWindowInformation()
            {
                Height = poeHeight,
                Width = poeWidth,
                ExpBarHeight = expBarHeight,
                FlaskBarHeight = flaskBarHeight,
                FlaskBarWidth = flaskBarWidth,
                Position = poePosition,
            };
        }

        #endregion
    }
}