/*
Copyright 2009-2022 Intel Corporation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace MeshCentralRouter
{
    public partial class KVMControl : UserControl
    {
        private bool remotepause = true;
        public Bitmap desktop = null;
        private Graphics desktopGraphics = null;
        public uint screenWidth = 0;
        public uint screenHeight = 0;
        private Pen RedPen = new Pen(System.Drawing.Color.Red);
        private Font DebugFont = new Font(FontFamily.GenericSansSerif, 14);
        private int compressionlevel = 60; // 60% compression
        private int scalinglevel = 1024; // 100% scale
        private int frameRate = 100; // Medium frame rate
        private bool swamMouseButtons = false;
        private bool remoteKeyboardMap = false;
        private bool autoSendClipboard = false;
        public bool AutoReconnect = false;
        private double scalefactor = 1;
        public List<ushort> displays = new List<ushort>();
        public ushort currentDisp = 0;
        public bool MouseButtonLeft = false;
        public bool MouseButtonMiddle = false;
        public bool MouseButtonRight = false;
        public double DpiX = 96;
        public double DpiY = 96;
        public KVMViewer parent = null;
        public KVMViewerExtra parentEx = null;
        private readonly KVMControlHook ControlHook;
        private readonly KVMControlHook.KVMCallback KeyboardCallback;
        private bool isHookWanted;
        private bool isHookPriority;
        private bool keyboardIsAttached;
        private long killNextKeyPress = 0;
        private bool controlLoaded = false;
        public Rectangle[] displayInfo = null;
        public Rectangle displayCrop = Rectangle.Empty;
        public Point displayOrigin = Point.Empty;


    //System level functions to be used for hook and unhook keyboard input  
    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int id, LowLevelKeyboardProc callback, IntPtr hMod, uint dwThreadId);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool UnhookWindowsHookEx(IntPtr hook);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hook, int nCode, IntPtr wp, IntPtr lp);
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    private static extern IntPtr GetModuleHandle(string name);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern short GetAsyncKeyState(Keys key);
    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();


    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
    [DllImport("gdi32.dll", ExactSpelling = true)] public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
    [DllImport("gdi32.dll", ExactSpelling = true)] public static extern IntPtr DeleteObject(IntPtr hObject);
    [DllImport("gdi32.dll")] private static extern bool BitBlt(IntPtr hdcDest, Int32 nXDest, Int32 nYDest, Int32 nWidth, Int32 nHeight, IntPtr hdcSrc, Int32 nXSrc, Int32 nYSrc, TernaryRasterOperations dwRop);
    [DllImport("gdi32.dll")] private static extern bool StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, TernaryRasterOperations dwRop);
    [DllImport("gdi32.dll")] static extern bool SetStretchBltMode(IntPtr hdc, StretchMode iStretchMode);
    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] private static extern bool DeleteDC(IntPtr hdc);
    public enum StretchMode
    {
      STRETCH_ANDSCANS = 1,
      STRETCH_ORSCANS = 2,
      STRETCH_DELETESCANS = 3,
      STRETCH_HALFTONE = 4,
    }

    public enum TernaryRasterOperations
    {
      SRCCOPY = 0x00CC0020, /* dest = source*/
      SRCPAINT = 0x00EE0086, /* dest = source OR dest*/
      SRCAND = 0x008800C6, /* dest = source AND dest*/
      SRCINVERT = 0x00660046, /* dest = source XOR dest*/
      SRCERASE = 0x00440328, /* dest = source AND (NOT dest )*/
      NOTSRCCOPY = 0x00330008, /* dest = (NOT source)*/
      NOTSRCERASE = 0x001100A6, /* dest = (NOT src) AND (NOT dest) */
      MERGECOPY = 0x00C000CA, /* dest = (source AND pattern)*/
      MERGEPAINT = 0x00BB0226, /* dest = (NOT source) OR dest*/
      PATCOPY = 0x00F00021, /* dest = pattern*/
      PATPAINT = 0x00FB0A09, /* dest = DPSnoo*/
      PATINVERT = 0x005A0049, /* dest = pattern XOR dest*/
      DSTINVERT = 0x00550009, /* dest = (NOT dest)*/
      BLACKNESS = 0x00000042, /* dest = BLACK*/
      WHITENESS = 0x00FF0062, /* dest = WHITE*/
    };

    private enum KvmCommands
        {
            Nop = 0,
            Key = 1,
            Mouse = 2,
            Picture = 3,
            Copy = 4,
            Compression = 5,
            Refresh = 6,
            Screen = 7,
            Pause = 8,
            TerminalText = 9,
            CtrlAltDel = 10,
            GetDisplays = 11,
            SetDisplay = 12,
            FrameRateTimer = 13,
            InitTouch = 14,
            Touch = 15,
            Message = 16,
            Jumbo = 27,
            Disconnect = 59,
            Alert = 65,
            DisplayInfo = 82,
            KeyUnicode = 85,
            MouseCursor = 88
        }

        private enum KvmMouseButtonCommands
        {
            MOUSEEVENTF_LEFTDOWN = 0x0002,
            MOUSEEVENTF_RIGHTDOWN = 0x0008,
            MOUSEEVENTF_MIDDLEDOWN = 0x0020,
            MOUSEEVENTF_LEFTUP = 0x0004,
            MOUSEEVENTF_RIGHTUP = 0x0010,
            MOUSEEVENTF_MIDDLEUP = 0x0040
        }

        [Category("Action")]
        [Description("Fires when the remote desktop size changes.")]
        public event EventHandler DesktopSizeChanged;

        [Category("Action")]
        [Description("Fires when it receives the display list.")]
        public event EventHandler DisplaysReceived;

        public delegate void ScreenAreaUpdatedHandler(Bitmap desktop, Rectangle r);
        public event ScreenAreaUpdatedHandler ScreenAreaUpdated;

        public enum ConnectState
        {
            Disconnected,
            Connecting,
            Connected
        }

        public int CompressionLevel { get { return compressionlevel; } set { compressionlevel = value; SendCompressionLevel(); } }
        public int ScalingLevel { get { return scalinglevel; } set { scalinglevel = value; SendCompressionLevel(); } }
        public int FrameRate { get { return frameRate; } set { frameRate = value; SendCompressionLevel(); } }
        public bool SwamMouseButtons { get { return swamMouseButtons; } set { swamMouseButtons = value; } }
        public bool RemoteKeyboardMap { get { return remoteKeyboardMap; } set { remoteKeyboardMap = value; } }
        public bool AutoSendClipboard { get { return autoSendClipboard; } set { autoSendClipboard = value; } }

        public double ScaleFactor { get { return scalefactor; } set { scalefactor = value; } }

        public void SetCompressionParams(int level, int scaling, int framerate) { compressionlevel = level; scalinglevel = scaling; frameRate = framerate; SendCompressionLevel(); }
        public int DesktopWidth
        {
            get { if (displayCrop == Rectangle.Empty) { return (int)screenWidth; } else { return displayCrop.Width; } }
        }
        public int DesktopHeight
        {
            get { if (displayCrop == Rectangle.Empty) { return (int)screenHeight; } else { return displayCrop.Height; } }
        }

        // Debug
        public int bytesent = 0;
        public int byterecv = 0;
        public int tilecount = 0;
        public int tilecopy = 0;
        public bool debugmode = false;

        public KVMControl()
        {
            InitializeComponent();
            this.SetStyle(ControlStyles.Selectable, false);
            this.SetStyle(ControlStyles.UserMouse, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.MouseWheel += new System.Windows.Forms.MouseEventHandler(KVMControl_MouseWheel);
            if (Settings.GetRegValue("Exp_KeyboardHook", false))
            {
                ControlHook = new KVMControlHook();
                KeyboardCallback = SendKey;
                isHookWanted = true;
            }
            else
            {
                isHookWanted = false;
            }
            isHookPriority = Settings.GetRegValue("Exp_KeyboardHookPriority", false);
        }

        public void AttachKeyboard()
        {
            //Console.WriteLine(isHookWanted);
            if (!keyboardIsAttached && isHookWanted)
            {
                ControlHook.AttachKeyboardHook(SendKey);
                keyboardIsAttached = true;
            }
        }

        public void DetacheKeyboard()
        {
            if (keyboardIsAttached)
            {
                ControlHook.DetachKeyboardHook();
                keyboardIsAttached = false;
            }
        }

        public int ProcessData(byte[] buffer, int off, int len)
        {
            //Console.Write("ProcessData " + off + ", " + len + "\r\n");
            int jumboHeaderSize = 0;

            if (len == 0) return 0;
            if (len >= 4)
            {
                // Decode the command header
                KvmCommands btype = (KvmCommands)((buffer[off] << 8) + buffer[off + 1]);
                int blen = (buffer[off + 2] << 8) + buffer[off + 3];
                if (len < blen) return 0;

                // Handle JUMBO command
                if ((btype == KvmCommands.Jumbo) && (blen == 8))
                {
                    jumboHeaderSize = 8;
                    blen = (int)IPAddress.NetworkToHostOrder((int)BitConverter.ToUInt32(buffer, off + 4));
                    off += 8;
                    btype = (KvmCommands)((buffer[off] << 8) + buffer[off + 1]);
                }

                // Process the command
                switch (btype)
                {
                    case KvmCommands.Screen:
                        {
                            if (blen != 8) return blen;
                            uint w = (ushort)((buffer[off + 4] << 8) + buffer[off + 5]);
                            uint h = (ushort)((buffer[off + 6] << 8) + buffer[off + 7]);
                            if (screenWidth == w && screenHeight == h) break;
                            screenWidth = w;
                            screenHeight = h;
                            desktop = new Bitmap((int)screenWidth, (int)screenHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                            desktopGraphics = Graphics.FromImage(desktop);
                            SendCompressionLevel();
                            Invoke(new SetSizeHandler(SetSize));
                            SendPause(false);
                            //SendMouse(2, KvmMouseButtonCommands.MOUSEEVENTF_LEFTUP);
                            //SendMouse(2, KvmMouseButtonCommands.MOUSEEVENTF_MIDDLEUP);
                            //SendMouse(2, KvmMouseButtonCommands.MOUSEEVENTF_RIGHTUP);
                            return blen + jumboHeaderSize;
                        }
                    case KvmCommands.Picture:
                        {
                            Image newtile;
                            ushort tile_x = (ushort)((buffer[off + 4] << 8) + buffer[off + 5]);
                            ushort tile_y = (ushort)((buffer[off + 6] << 8) + buffer[off + 7]);
                            try { newtile = Image.FromStream(new System.IO.MemoryStream(buffer, off + 8, blen - 8)); } catch (Exception) { return blen; }
                            //Rectangle r = new Rectangle((int)tile_x, (int)tile_y, newtile.Width, newtile.Height);
                            //Rectangle r3 = new Rectangle((int)Math.Round((double)tile_x / (double)scalefactor) - 2, (int)Math.Round((double)tile_y / (double)scalefactor) - 2, (int)Math.Round((double)newtile.Width / (double)scalefactor) + 4, (int)Math.Round((double)newtile.Height / (double)scalefactor) + 4);
                            tilecount++;

                            // Winform mode
                            if (desktop != null)
                            {
                                lock (desktop)
                                {
                                    Graphics MemGraphics = Graphics.FromImage(newtile);

                                    IntPtr hBitmap = ((Bitmap)newtile).GetHbitmap();
                                    IntPtr memdc = MemGraphics.GetHdc();
                                    IntPtr pOrig = SelectObject(memdc, hBitmap);
                                    IntPtr dcDst = desktopGraphics.GetHdc();


                                    bool bok = BitBlt(dcDst, (int)tile_x, (int)tile_y, (int)newtile.Width, (int)newtile.Height, memdc, (int)0, (int)0, TernaryRasterOperations.SRCCOPY);

                                    IntPtr pNew = SelectObject(memdc, pOrig); // == hBitmap
                                    DeleteObject(pNew);
                                    desktopGraphics.ReleaseHdc(dcDst);
                                    MemGraphics.ReleaseHdc(memdc);
                                    if(debugmode)
                                    {
                                        desktopGraphics.DrawRectangle(RedPen, new Rectangle((int)tile_x, (int)tile_y, newtile.Width - 1, newtile.Height - 1));
                                        desktopGraphics.DrawString(string.Format("{0} / {1}kb", tilecount, blen / 2014), DebugFont, RedPen.Brush, new Point((int)tile_x, (int)tile_y));
                                    }
                                }

                                // Update extra displays if needed
                                Rectangle r = new Rectangle((int)tile_x, (int)tile_y, newtile.Width, newtile.Height);
                                Rectangle rx = new Rectangle(r.X + displayOrigin.X, r.Y + displayOrigin.Y, r.Width, r.Height);
                                //Console.WriteLine(rx.ToString());
                                if (ScreenAreaUpdated != null) ScreenAreaUpdated(desktop, rx);

                                if (displayCrop == Rectangle.Empty)
                                {
                                    if (scalefactor == 1)
                                    {
                                        Invalidate(r, false);
                                    }
                                    else
                                    {
                                        Rectangle r3 = new Rectangle((int)((double)tile_x / (double)scalefactor) - 2, (int)((double)tile_y / (double)scalefactor) - 2, (int)((double)newtile.Width / (double)scalefactor) + 4, (int)((double)newtile.Height / (double)scalefactor) + 4);
                                        Invalidate(r3, false);
                                    }
                                }
                                else if (displayCrop.IntersectsWith(rx) == true)
                                {
                                    Rectangle r2 = new Rectangle(rx.X, rx.Y, rx.Width, rx.Height);
                                    r2.Intersect(displayCrop);

                                    if (scalefactor == 1)
                                    {
                                        Rectangle r3 = new Rectangle(r2.X - displayCrop.X, r2.Y - displayCrop.Y, r2.Width, r2.Height);
                                        Invalidate(r, false);
                                    }
                                    else
                                    {
                                        Rectangle r3 = new Rectangle((int)((double)(r2.X - displayCrop.X) / (double)scalefactor) - 2, (int)((double)(r2.Y - displayCrop.Y) / (double)scalefactor) - 2, (int)((double)r2.Width / (double)scalefactor) + 4, (int)((double)r2.Height / (double)scalefactor) + 4);
                                        Invalidate(r3, false);
                                    }
                                }
                            }

                            return blen + jumboHeaderSize;
                        }
                    case KvmCommands.Copy:
                        {
                            ushort sourcex = (ushort)((buffer[off + 4] << 8) + buffer[off + 5]);
                            ushort sourcey = (ushort)((buffer[off + 6] << 8) + buffer[off + 7]);
                            ushort targetx = (ushort)((buffer[off + 8] << 8) + buffer[off + 9]);
                            ushort targety = (ushort)((buffer[off + 10] << 8) + buffer[off + 11]);
                            ushort tilew = (ushort)((buffer[off + 12] << 8) + buffer[off + 13]);
                            ushort tileh = (ushort)((buffer[off + 14] << 8) + buffer[off + 15]);
                            Rectangle r1 = new Rectangle((int)sourcex, (int)sourcey, (int)tilew, (int)tileh);
                            Rectangle r2 = new Rectangle((int)targetx, (int)targety, (int)tilew, (int)tileh);
                            Rectangle r3 = new Rectangle((int)((double)targetx / (double)scalefactor) - 2, (int)((double)targety / (double)scalefactor) - 2, (int)((double)tilew / (double)scalefactor) + 4, (int)((double)tileh / (double)scalefactor) + 4);

                            // WinForm mode
                            if (desktop != null)
                            {
                                lock (desktop)
                                {
                                    Bitmap bmp = new Bitmap(tilew, tileh);
                                    Graphics g = Graphics.FromImage(bmp);
                                    g.DrawImage(desktop, 0, 0, r1, GraphicsUnit.Pixel);
                                    g.Dispose();
                                    desktopGraphics.Flush();
                                    desktopGraphics.DrawImage(bmp, r2, 0, 0, tilew, tileh, GraphicsUnit.Pixel);
                                    if (debugmode) { desktopGraphics.DrawString("COPY", DebugFont, RedPen.Brush, new Point((int)targetx, (int)targety + 20)); }
                                }
                                tilecopy++;
                            }

                            return blen + jumboHeaderSize;
                        }
                    case KvmCommands.GetDisplays:
                        {
                            int i = 0;
                            ushort length = (ushort)((buffer[off + 4] << 8) + buffer[off + 5]);
                            displays.Clear();
                            if (length > 0) { for (i = 0; i < length; i++) { displays.Add((ushort)((buffer[off + 6 + i * 2] << 8) + buffer[off + 7 + i * 2])); } }
                            currentDisp = (ushort)((buffer[off + 6 + i * 2] << 8) + buffer[off + 7 + i * 2]);
                            if (DisplaysReceived != null) DisplaysReceived(this, null);
                            break;
                        }
                    case KvmCommands.SetDisplay:
                        {
                            currentDisp = (ushort)((buffer[off + 4] << 8) + buffer[off + 5]);
                            break;
                        }
                    case KvmCommands.MouseCursor:
                        {
                            if (blen != 5) return blen;
                            ChangeMouseCursor(buffer[off + 4]);
                            break;
                        }
                    case KvmCommands.DisplayInfo:
                        {
                            if ((blen < 4) || (((blen - 4) % 10) != 0)) break;
                            int screenCount = ((blen - 4) / 10);
                            int ptr = off + 4;
                            Rectangle[] xDisplayInfo = new Rectangle[screenCount];

                            for (var i = 0; i < screenCount; i++)
                            {
                                int id = ((buffer[ptr + 0] << 8) + buffer[ptr + 1]);
                                int x = ((buffer[ptr + 2] << 8) + buffer[ptr + 3]);
                                int y = ((buffer[ptr + 4] << 8) + buffer[ptr + 5]);
                                int w = ((buffer[ptr + 6] << 8) + buffer[ptr + 7]);
                                int h = ((buffer[ptr + 8] << 8) + buffer[ptr + 9]);
                                if (x > 32766) { x -= 65536; }
                                if (y > 32766) { y -= 65536; }
                                Rectangle r = new Rectangle(x, y, w, h);
                                xDisplayInfo[id - 1] = r;
                                ptr += 10;
                            }

                            // Set display information
                            displayInfo = xDisplayInfo;
                            break;
                        }
                    default:
                        {
                            // MessageBox.Show("Should never happen!");
                            //Disconnect();
                            return 0;
                        }
                }
                return blen + jumboHeaderSize;
            }
            return 0;
        }

        private delegate void ChangeMouseCursorHandler(int cursorId);
        private void ChangeMouseCursor(int cursorId)
        {
            if (this.InvokeRequired) { this.Invoke(new ChangeMouseCursorHandler(ChangeMouseCursor), cursorId); return; }
            if (cursorId == 0) { this.Cursor = System.Windows.Forms.Cursors.Default; return; } // default
            if (cursorId == 1) { this.Cursor = System.Windows.Forms.Cursors.AppStarting; return; } // progress
            if (cursorId == 2) { this.Cursor = System.Windows.Forms.Cursors.Cross; return; } // crosshair
            if (cursorId == 3) { this.Cursor = System.Windows.Forms.Cursors.Default; return; } // pointer
            if (cursorId == 4) { this.Cursor = System.Windows.Forms.Cursors.Help; return; } // help
            if (cursorId == 5) { this.Cursor = System.Windows.Forms.Cursors.IBeam; return; } // text
            if (cursorId == 6) { this.Cursor = System.Windows.Forms.Cursors.No; return; } // no-drop
            if (cursorId == 7) { this.Cursor = System.Windows.Forms.Cursors.Arrow; return; } // move
            if (cursorId == 8) { this.Cursor = System.Windows.Forms.Cursors.SizeNESW; return; } // nesw-resize
            if (cursorId == 9) { this.Cursor = System.Windows.Forms.Cursors.SizeNS; return; } // ns-resize
            if (cursorId == 10) { this.Cursor = System.Windows.Forms.Cursors.SizeNWSE; return; } // nwse-resize
            if (cursorId == 11) { this.Cursor = System.Windows.Forms.Cursors.SizeWE; return; } // w-resize
            if (cursorId == 12) { this.Cursor = System.Windows.Forms.Cursors.Arrow; return; } // alias
            if (cursorId == 13) { this.Cursor = System.Windows.Forms.Cursors.WaitCursor; return; } // wait
            if (cursorId == 14) { this.Cursor = System.Windows.Forms.Cursors.Default; return; } // none
            if (cursorId == 15) { this.Cursor = System.Windows.Forms.Cursors.No; return; } // not-allowed
            if (cursorId == 16) { this.Cursor = System.Windows.Forms.Cursors.VSplit; return; } // col-resize
            if (cursorId == 17) { this.Cursor = System.Windows.Forms.Cursors.HSplit; return; } // row-resize
            if (cursorId == 18) { this.Cursor = System.Windows.Forms.Cursors.Default; return; } // copy
            if (cursorId == 19) { this.Cursor = System.Windows.Forms.Cursors.SizeAll; return; } // zoom-in
            if (cursorId == 20) { this.Cursor = System.Windows.Forms.Cursors.SizeAll; return; } // zoom-out
        }

        public void Repaint(Region region)
        {
            Invalidate(region);
        }

        public void Repaint(Rectangle rect)
        {
            Invalidate(rect);
        }

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // Do not paint background.
        }

        private delegate void SetSizeHandler();
        private void SetSize()
        {
            if (DesktopSizeChanged != null) { DesktopSizeChanged(this, null); }
        }

        private void KVMControl_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            if (desktop == null)
            {
                g.FillRectangle(new SolidBrush(Color.Black), e.ClipRectangle);
            }
            else
            {
                lock (desktop)
                {
                    if (displayCrop == Rectangle.Empty) // No cropping
                    {
                        if (scalefactor == 1)
                        {
                            g.DrawImage(desktop, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
                        }
                        else
                        {
                            g.DrawImage((Image)desktop, e.ClipRectangle, (int)((double)e.ClipRectangle.Left * (double)scalefactor), (int)((double)e.ClipRectangle.Top * (double)scalefactor), (int)((double)e.ClipRectangle.Width * (double)scalefactor), (int)((double)e.ClipRectangle.Height * (double)scalefactor), GraphicsUnit.Pixel);
                        }
                    }
                    else
                    {
                        if (scalefactor == 1) // Cropping in effect, this is when we show different displays in different windows
                        {
                            g.DrawImage(desktop, e.ClipRectangle, new Rectangle(e.ClipRectangle.X - displayOrigin.X + displayCrop.X, e.ClipRectangle.Y - displayOrigin.Y + displayCrop.Y, e.ClipRectangle.Width, e.ClipRectangle.Height), GraphicsUnit.Pixel);
                        }
                        else
                        {
                            Rectangle srcRect = new Rectangle((int)((double)(e.ClipRectangle.Left) * (double)scalefactor) - displayOrigin.X + displayCrop.X, (int)((double)(e.ClipRectangle.Top) * (double)scalefactor) - displayOrigin.Y + displayCrop.Y, (int)((double)e.ClipRectangle.Width * (double)scalefactor), (int)((double)e.ClipRectangle.Height * (double)scalefactor));
                            g.DrawImage((Image)desktop, e.ClipRectangle, srcRect, GraphicsUnit.Pixel);
                        }
                    }
                }
            }
        }

        public void SendKey(byte key, byte action)
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Key));
            bw.Write(IPAddress.HostToNetworkOrder((short)6));
            bw.Write((byte)action);
            bw.Write((byte)key);
            Send(bw);
        }

        public void SendUnicodeKey(ushort key, byte action)
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.KeyUnicode));
            bw.Write(IPAddress.HostToNetworkOrder((short)7));
            bw.Write((byte)action);
            bw.Write((byte)(key >> 8));
            bw.Write((byte)(key & 0xFF));
            Send(bw);
        }

        private void SendPress(KeyPressEventArgs e, byte action)
        {
            //if (state != ConnectState.Connected) return;

            if (remoteKeyboardMap == true) return;

            if (killNextKeyPress > 0)
            {
                long t = DateTime.Now.Ticks;
                if ((t - killNextKeyPress) < 10) { killNextKeyPress = 0; return; }
            }

            ushort c = (ushort)e.KeyChar;
            if (c >= 32) { SendUnicodeKey(c, 0); }
        }

        private static readonly Keys[] ignoreKeys = {
            // 0-9 (KeyCode: 48-57)
            Keys.D0, Keys.D1, Keys.D2, Keys.D3, Keys.D4, Keys.D5, Keys.D6, Keys.D7, Keys.D8, Keys.D9,
            // NumPad 0-9 (KeyCode: 96-105)
            Keys.NumPad0, Keys.NumPad1, Keys.NumPad2, Keys.NumPad3, Keys.NumPad4, Keys.NumPad5, Keys.NumPad6, Keys.NumPad7, Keys.NumPad8, Keys.NumPad9,
            // Special-Keys
            Keys.Space, Keys.Divide, Keys.Multiply, Keys.Subtract, Keys.Add, Keys.Decimal,
            // Oem-Keys
            Keys.Oem1, Keys.Oem2, Keys.Oem3, Keys.Oem4, Keys.Oem5, Keys.Oem6, Keys.Oem7, Keys.Oem8, Keys.Oem102, Keys.OemSemicolon, Keys.Oemplus, Keys.Oemcomma,
            Keys.OemMinus, Keys.OemPeriod, Keys.OemQuestion, Keys.Oemtilde, Keys.OemOpenBrackets, Keys.OemPipe, Keys.OemCloseBrackets, Keys.OemQuotes
        };
        private void SendKey(KeyEventArgs e, byte action)
        {
            //if (state != ConnectState.Connected) return;

            if (remoteKeyboardMap == true) { SendKey((byte)e.KeyCode, action); return; } // Use old key system that uses the remote keyboard mapping.
            string keycode = e.KeyCode.ToString();
            if ((action == 0) && (e.Control == false) && (e.Alt == false) && (Array.IndexOf(ignoreKeys, e.KeyCode) > -1 || (keycode.Length == 1))) return;
            if ((e.Control == true) || (e.Alt == true)) { killNextKeyPress = DateTime.Now.Ticks; }
            SendKey((byte)e.KeyCode, action);
            e.Handled = true;
        }

        private short LastX = 0;
        private short LastY = 0;
        private void SendMouse(MouseEventArgs e, byte action)
        {
            //if (state != ConnectState.Connected) return;
            byte buttons = 0;
            if (action == 1)
            {
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        if (swamMouseButtons)
                        {
                            buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_RIGHTDOWN;
                        }
                        else
                        {
                            buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_LEFTDOWN;
                        }
                        break;
                    case MouseButtons.Right:
                        if (swamMouseButtons)
                        {
                            buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_LEFTDOWN;
                        }
                        else
                        {
                            buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_RIGHTDOWN;
                        }
                        break;
                    case MouseButtons.Middle:
                        buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_MIDDLEDOWN;
                        break;
                }
            }
            else if (action == 2)
            {
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        if (swamMouseButtons) { buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_RIGHTUP; } else { buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_LEFTUP; }
                        break;
                    case MouseButtons.Right:
                        if (swamMouseButtons) { buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_LEFTUP; } else { buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_RIGHTUP; }
                        break;
                    case MouseButtons.Middle:
                        buttons |= (byte)KvmMouseButtonCommands.MOUSEEVENTF_MIDDLEUP;
                        break;
                }
            }

            if (displayCrop == Rectangle.Empty)
            {
                LastX = (short)((double)e.X * (double)scalefactor);
                LastY = (short)((double)e.Y * (double)scalefactor);
            }
            else
            {
                LastX = (short)((int)((double)e.X * (double)scalefactor) - displayOrigin.X + displayCrop.X);
                LastY = (short)((int)((double)e.Y * (double)scalefactor) - displayOrigin.Y + displayCrop.Y);
            }

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Mouse));
            bw.Write(IPAddress.HostToNetworkOrder((short)(e.Delta == 0 ? 10 : 12)));
            bw.Write((byte)action);
            bw.Write((byte)buttons);
            bw.Write(IPAddress.HostToNetworkOrder(LastX));
            bw.Write(IPAddress.HostToNetworkOrder(LastY));
            if (e.Delta != 0) { bw.Write(IPAddress.HostToNetworkOrder((short)e.Delta)); }
            Send(bw);
        }

        public void SendMouseWheel(MouseEventArgs e, byte action)
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Mouse));
            bw.Write(IPAddress.HostToNetworkOrder((short)(e.Delta == 0 ? 10 : 12)));
            bw.Write((short)0);
            bw.Write(IPAddress.HostToNetworkOrder(LastX));
            bw.Write(IPAddress.HostToNetworkOrder(LastY));
            if (e.Delta != 0) { bw.Write(IPAddress.HostToNetworkOrder((short)e.Delta)); }
            Send(bw);
        }

        private void SendMouse(byte action, KvmMouseButtonCommands buttons)
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Mouse));
            bw.Write(IPAddress.HostToNetworkOrder((short)10));
            bw.Write((byte)action);
            bw.Write((byte)buttons);
            bw.Write((short)0);
            bw.Write((short)0);
            Send(bw);
        }

        public void SendCompressionLevel()
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Compression));
            bw.Write(IPAddress.HostToNetworkOrder((short)10));
            bw.Write((byte)1);                                                                      // Image Type (Reserved, 1 = JPEG)
            bw.Write((byte)compressionlevel);                                                       // Compression Level
            bw.Write(IPAddress.HostToNetworkOrder((short)scalinglevel));                            // Scaling
            bw.Write(IPAddress.HostToNetworkOrder((short)frameRate));                               // Frame Rate
            Send(bw);
        }

        public void SendRefresh()
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Refresh));
            bw.Write(IPAddress.HostToNetworkOrder((short)4));
            Send(bw);
        }

        public void SendPause(bool paused)
        {
            //if (remotepauseactive == false || paused == remotepause) return;
            //if (paused == remotepause) return;
            remotepause = paused;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.Pause));
            bw.Write(IPAddress.HostToNetworkOrder((short)5));
            bw.Write(paused ? (byte)1 : (byte)0);
            Send(bw);
        }

        public void Send(byte[] buffer)
        {
            //if (state == ConnectState.Disconnected) return;
            try
            {
                if (parent != null)
                {
                    parent.bytesOut += buffer.Length;
                    parent.bytesOutCompressed += parent.wc.SendBinary(buffer, 0, buffer.Length);
                    bytesent += buffer.Length;
                }
                else if (parentEx != null)
                {
                    parentEx.mainKvmControl.Send(buffer);
                }
            }
            catch (Exception) { }
        }

        public void Send(string str)
        {
            //if (state == ConnectState.Disconnected) return;
            try
            {
                if (parent != null)
                {
                    parent.bytesOut += str.Length;
                    parent.bytesOutCompressed += parent.wc.SendString(str);
                    bytesent += str.Length;
                }
                else if (parentEx != null)
                {
                    parentEx.mainKvmControl.Send(str);
                }
            }
            catch (Exception) { }
        }

        public void Send(BinaryWriter bw)
        {
            //if (state == ConnectState.Disconnected) { RecycleBinaryWriter(bw); return; }
            if ((parent != null) && (parent.wc != null))
            {
                try
                {
                    parent.bytesOut += (int)((MemoryStream)bw.BaseStream).Length;
                    parent.bytesOutCompressed += parent.wc.SendBinary(((MemoryStream)bw.BaseStream).GetBuffer(), 0, (int)((MemoryStream)bw.BaseStream).Length);
                    bytesent += (int)((MemoryStream)bw.BaseStream).Length;
                }
                catch (Exception) { }
            }
            else if (parentEx != null)
            {
                parentEx.mainKvmControl.Send(bw);
            }
            RecycleBinaryWriter(bw);
        }

        private void KVMControl_MouseDown(object sender, MouseEventArgs e)
        {
            SendMouse(e, 1);
        }

        private void KVMControl_MouseUp(object sender, MouseEventArgs e)
        {
            SendMouse(e, 2);
        }

        long lastMouseMove = 0;

        private void KVMControl_MouseMove(object sender, MouseEventArgs e)
        {
            long ct = DateTime.Now.Ticks;
            if ((lastMouseMove + 30) < ct)
            {
                lastMouseMove = ct;
                SendMouse(e, 0);
            }
        }

        public void KVMControl_MouseWheel(object sender, MouseEventArgs e)
        {
            SendMouse(e, 0);
        }

        private void KVMControl_MouseEnter(object sender, EventArgs e)
        {
            this.Focus();
        }

        public void SendCtrlAltDel()
        {
            //if (state != ConnectState.Connected) return;
            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.CtrlAltDel));
            bw.Write(IPAddress.HostToNetworkOrder((short)4));
            Send(bw);
        }

        public void GetDisplayNumbers()
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.GetDisplays));
            bw.Write(IPAddress.HostToNetworkOrder((short)4));
            Send(bw);
        }

        public void SendDisplay(int dispNo)
        {
            //if (state != ConnectState.Connected) return;

            BinaryWriter bw = GetBinaryWriter();
            bw.Write(IPAddress.HostToNetworkOrder((short)KvmCommands.SetDisplay));
            bw.Write(IPAddress.HostToNetworkOrder((short)6));
            bw.Write(IPAddress.HostToNetworkOrder((short)dispNo));
            Send(bw);
        }

        public void SendCharmsKey()
        {
            SendKey(0x5B, 4);   // Windows key down
            SendKey(67, 1);     // C down
            SendKey(67, 2);     // C up
            SendKey(0x5B, 3);   // Windows key up
        }

        // BinaryWriter Object Recycling System
        private static Stack<BinaryWriter> BinaryWriteRecycleList = new Stack<BinaryWriter>();
        public static BinaryWriter GetBinaryWriter() { lock (BinaryWriteRecycleList) { return (BinaryWriteRecycleList.Count == 0) ? new BinaryWriter(new MemoryStream(32000), Encoding.UTF8) : BinaryWriteRecycleList.Pop(); } }
        public static void RecycleBinaryWriter(BinaryWriter obj) { lock (BinaryWriteRecycleList) { ((MemoryStream)obj.BaseStream).SetLength(0); BinaryWriteRecycleList.Push(obj); } }

        // MemoryStream Object Recycling System
        private static Stack<MemoryStream> MemoryStreamRecycleList = new Stack<MemoryStream>();
        public static MemoryStream GetMemoryStream() { lock (MemoryStreamRecycleList) { return (MemoryStreamRecycleList.Count == 0) ? new MemoryStream(32000) : MemoryStreamRecycleList.Pop(); } }
        public static void RecycleMemoryStream(MemoryStream obj) { lock (MemoryStreamRecycleList) { obj.SetLength(0); MemoryStreamRecycleList.Push(obj); } }

        // StringBuilder Object Recycling System
        private static Stack<StringBuilder> StringBuilderRecycleList = new Stack<StringBuilder>();
        public static StringBuilder GetStringBuilder() { lock (StringBuilderRecycleList) { return (StringBuilderRecycleList.Count == 0) ? new StringBuilder(16000) : StringBuilderRecycleList.Pop(); } }
        public static void RecycleStringBuilder(StringBuilder obj) { lock (StringBuilderRecycleList) { obj.Length = 0; StringBuilderRecycleList.Push(obj); } }

        // byte[] Object Recycling System
        private static Stack<byte[]> ByteArrayRecycleList = new Stack<byte[]>();
        private static int ByteArrayRecycleListCount = 0;
        public static byte[] GetByteArray() { lock (ByteArrayRecycleList) { if (ByteArrayRecycleList.Count == 0) ByteArrayRecycleListCount++; return (ByteArrayRecycleList.Count == 0) ? new byte[65535] : ByteArrayRecycleList.Pop(); } }
        public static void RecycleByteArray(byte[] obj) { lock (ByteArrayRecycleList) { if (obj != null) ByteArrayRecycleList.Push(obj); } }

        private void KVMControl_KeyDown(object sender, KeyEventArgs e)
        {
            if (!isHookPriority)
            {
                if ((e.KeyCode == Keys.LWin) || (e.KeyCode == Keys.RWin)) return; // Don't process the Windows key
                SendKey(e, 0);
                e.Handled = true;
            }
        }

        private void KVMControl_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!isHookPriority)
            {
                SendPress(e, 0);
                e.Handled = true;
            }
        }

        private void KVMControl_KeyUp(object sender, KeyEventArgs e)
        {
            if (!isHookPriority)
            {
                SendKey(e, 1);
                e.Handled = true;
            }
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            const int WM_KEYDOWN = 0x100;
            const int WM_SYSKEYDOWN = 0x104;

            // Tab keys
            if (msg.Msg == WM_KEYDOWN && msg.WParam.ToInt32() == 9)
            {
                SendKey((byte)msg.WParam.ToInt32(), 0);
                return true;
            }

            // Handle arrow keys
            if (((msg.Msg == WM_KEYDOWN) || (msg.Msg == WM_SYSKEYDOWN)) && msg.WParam.ToInt32() >= 37 && msg.WParam.ToInt32() <= 40)
            {
                SendKey((byte)msg.WParam.ToInt32(), 0);
                return true;
            }

            return false;
        }

    // Structure contain information about low-level keyboard input event 
    private struct KBDLLHOOKSTRUCT
    {
      public Keys key;
      public int scanCode;
      public int flags;
      public int time;
      public IntPtr extra;
    }

    public void cropDisplay(Point o, Rectangle r)
    {
        if (IsDisposed || Disposing) return;
        displayCrop = r;
        displayOrigin = o;
        if (controlLoaded)
        {
            Invoke(new SetSizeHandler(SetSize));
            Invalidate();
        }
    }

    private void KVMControl_Load(object sender, EventArgs e)
    {
        controlLoaded = true;
        ProcessModule objCurrentModule = Process.GetCurrentProcess().MainModule;
        objKeyboardProcess = new LowLevelKeyboardProc(captureKey);
        ptrHook = SetWindowsHookEx(13, objKeyboardProcess, GetModuleHandle(objCurrentModule.ModuleName), 0);
    }

    //Declaring Global objects     
    private IntPtr ptrHook;
    private LowLevelKeyboardProc objKeyboardProcess;

    private IntPtr captureKey(int nCode, IntPtr wp, IntPtr lp)
    {
      bool bIsForegroundWindow = false;
      if(this.ParentForm != null) {
        try { bIsForegroundWindow = GetForegroundWindow() == this.ParentForm.Handle; } catch { }
      }
      if(nCode >= 0 && bIsForegroundWindow)
      {
        KBDLLHOOKSTRUCT objKeyInfo = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lp, typeof(KBDLLHOOKSTRUCT));

        // Disabling Windows keys 

        if(objKeyInfo.key == Keys.RWin || objKeyInfo.key == Keys.LWin)
        {
          bool bKeyDown = wp == (IntPtr)0x0100/*WM_KEYDOWN*/;
          SendKey(new KeyEventArgs((Keys)objKeyInfo.key), (byte)(bKeyDown ? 0 : 1));
          //        SendKey(new KeyEventArgs((Keys)objKeyInfo.key), 0);
          return (IntPtr)1; // if 0 is returned then All the above keys will be enabled
                            //      return (IntPtr)0; // if 0 is returned then All the above keys will be enabled


        }
      }
      return CallNextHookEx(ptrHook, nCode, wp, lp);
    }

    bool HasAltModifier(int flags)
    {
      return (flags & 0x20) == 0x20;
    }

  }
}
