/*
Copyright 2009-2021 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;

namespace MeshCentralRouter
{
    public partial class KVMControl : UserControl
    {
        private bool remotepause = true;
        private 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;
        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;
        private readonly KVMControlHook ControlHook;
        private readonly KVMControlHook.KVMCallback KeyboardCallback;
        private bool isHookWanted;
        private bool isHookPriority;
        private bool keyboardIsAttached;
        private long killNextKeyPress = 0;


        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,
            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 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 { return (int)screenWidth; } }
        public int DesktopHeight { get { return (int)screenHeight; } }

        // 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)((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);
                            tilecount++;

                            // Winform mode
                            if (desktop != null)
                            {
                                lock (desktop)
                                {
                                    desktopGraphics.DrawImage(newtile, new Rectangle((int)tile_x, (int)tile_y, newtile.Width, newtile.Height), new Rectangle(0, 0, newtile.Width, newtile.Height), GraphicsUnit.Pixel);
                                    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));
                                    }
                                }
                                if (scalefactor == 1) Invalidate(r, false); else 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;
                        }
                    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);
        }

        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 (scalefactor == 1)
                    {
                        g.DrawImage(desktop, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
                    }
                    else
                    {
                        //g.DrawImage((Image)desktop, new Rectangle(0, 0, Width, Height), 0, 0, screenWidth, screenHeight, GraphicsUnit.Pixel);
                        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);
                    }
                }
            }
        }

        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)
            {
                SendKey((byte)c, 0);
            }
            else
            {
                SendUnicodeKey(c, 0);
            }
        }

        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) && (((e.KeyValue >= 48) && (e.KeyValue <= 57)) || (keycode.Length == 1) || (keycode.StartsWith("Oem") == true))) 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;
                }
            }

            LastX = (short)((double)e.X * (double)scalefactor);
            LastY = (short)((double)e.Y * (double)scalefactor);

            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
            {
                parent.bytesOut += buffer.Length;
                parent.bytesOutCompressed += parent.wc.SendBinary(buffer, 0, buffer.Length);
                bytesent += buffer.Length;
            }
            catch (Exception) { }
        }

        public void Send(string str)
        {
            //if (state == ConnectState.Disconnected) return;
            try
            {
                parent.bytesOut += str.Length;
                parent.bytesOutCompressed += parent.wc.SendString(str);
                bytesent += str.Length;
            }
            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) { }
            }
            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;
        }
    }
}
