using Gma.System.MouseKeyHook; using Quasar.Common.Enums; using Quasar.Common.Helpers; using Quasar.Common.Messages; using Quasar.Server.Helper; using Quasar.Server.Messages; using Quasar.Server.Networking; using Quasar.Server.Utilities; using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; namespace Quasar.Server.Forms { public partial class FrmRemoteDesktop : Form { /// /// States whether remote mouse input is enabled. /// private bool _enableMouseInput; /// /// States whether remote keyboard input is enabled. /// private bool _enableKeyboardInput; /// /// Holds the state of the local keyboard hooks. /// private IKeyboardMouseEvents _keyboardHook; /// /// Holds the state of the local mouse hooks. /// private IKeyboardMouseEvents _mouseHook; /// /// A list of pressed keys for synchronization between key down & -up events. /// private readonly List _keysPressed; /// /// The client which can be used for the remote desktop. /// private readonly Client _connectClient; /// /// The message handler for handling the communication with the client. /// private readonly RemoteDesktopHandler _remoteDesktopHandler; /// /// Holds the opened remote desktop form for each client. /// private static readonly Dictionary OpenedForms = new Dictionary(); /// /// Creates a new remote desktop form for the client or gets the current open form, if there exists one already. /// /// The client used for the remote desktop form. /// /// Returns a new remote desktop form for the client if there is none currently open, otherwise creates a new one. /// public static FrmRemoteDesktop CreateNewOrGetExisting(Client client) { if (OpenedForms.ContainsKey(client)) { return OpenedForms[client]; } FrmRemoteDesktop r = new FrmRemoteDesktop(client); r.Disposed += (sender, args) => OpenedForms.Remove(client); OpenedForms.Add(client, r); return r; } /// /// Initializes a new instance of the class using the given client. /// /// The client used for the remote desktop form. public FrmRemoteDesktop(Client client) { _connectClient = client; _remoteDesktopHandler = new RemoteDesktopHandler(client); _keysPressed = new List(); RegisterMessageHandler(); InitializeComponent(); } /// /// Called whenever a client disconnects. /// /// The client which disconnected. /// True if the client connected, false if disconnected private void ClientDisconnected(Client client, bool connected) { if (!connected) { this.Invoke((MethodInvoker)this.Close); } } /// /// Registers the remote desktop message handler for client communication. /// private void RegisterMessageHandler() { _connectClient.ClientState += ClientDisconnected; _remoteDesktopHandler.DisplaysChanged += DisplaysChanged; _remoteDesktopHandler.ProgressChanged += UpdateImage; MessageHandler.Register(_remoteDesktopHandler); } /// /// Unregisters the remote desktop message handler. /// private void UnregisterMessageHandler() { MessageHandler.Unregister(_remoteDesktopHandler); _remoteDesktopHandler.DisplaysChanged -= DisplaysChanged; _remoteDesktopHandler.ProgressChanged -= UpdateImage; _connectClient.ClientState -= ClientDisconnected; } /// /// Subscribes to local mouse and keyboard events for remote desktop input. /// private void SubscribeEvents() { // TODO: Check Hook.GlobalEvents vs Hook.AppEvents below // TODO: Maybe replace library with .NET events like on Linux if (PlatformHelper.RunningOnMono) // Mono/Linux { this.KeyDown += OnKeyDown; this.KeyUp += OnKeyUp; } else // Windows { _keyboardHook = Hook.GlobalEvents(); _keyboardHook.KeyDown += OnKeyDown; _keyboardHook.KeyUp += OnKeyUp; _mouseHook = Hook.AppEvents(); _mouseHook.MouseWheel += OnMouseWheelMove; } } /// /// Unsubscribes from local mouse and keyboard events. /// private void UnsubscribeEvents() { if (PlatformHelper.RunningOnMono) // Mono/Linux { this.KeyDown -= OnKeyDown; this.KeyUp -= OnKeyUp; } else // Windows { if (_keyboardHook != null) { _keyboardHook.KeyDown -= OnKeyDown; _keyboardHook.KeyUp -= OnKeyUp; _keyboardHook.Dispose(); } if (_mouseHook != null) { _mouseHook.MouseWheel -= OnMouseWheelMove; _mouseHook.Dispose(); } } } /// /// Starts the remote desktop stream and begin to receive desktop frames. /// private void StartStream() { ToggleConfigurationControls(true); picDesktop.Start(); // Subscribe to the new frame counter. picDesktop.SetFrameUpdatedEvent(frameCounter_FrameUpdated); this.ActiveControl = picDesktop; _remoteDesktopHandler.BeginReceiveFrames(barQuality.Value, cbMonitors.SelectedIndex); } /// /// Stops the remote desktop stream. /// private void StopStream() { ToggleConfigurationControls(false); picDesktop.Stop(); // Unsubscribe from the frame counter. It will be re-created when starting again. picDesktop.UnsetFrameUpdatedEvent(frameCounter_FrameUpdated); this.ActiveControl = picDesktop; _remoteDesktopHandler.EndReceiveFrames(); } /// /// Toggles the activatability of configuration controls in the status/configuration panel. /// /// When set to true the configuration controls get enabled, otherwise they get disabled. private void ToggleConfigurationControls(bool started) { btnStart.Enabled = !started; btnStop.Enabled = started; barQuality.Enabled = !started; cbMonitors.Enabled = !started; } /// /// Toggles the visibility of the status/configuration panel. /// /// Decides if the panel should be visible. private void TogglePanelVisibility(bool visible) { panelTop.Visible = visible; btnShow.Visible = !visible; this.ActiveControl = picDesktop; } /// /// Called whenever the remote displays changed. /// /// The message handler which raised the event. /// The currently available displays. private void DisplaysChanged(object sender, int displays) { cbMonitors.Items.Clear(); for (int i = 0; i < displays; i++) cbMonitors.Items.Add($"Display {i + 1}"); cbMonitors.SelectedIndex = 0; } /// /// Updates the current desktop image by drawing it to the desktop picturebox. /// /// The message handler which raised the event. /// The new desktop image to draw. private void UpdateImage(object sender, Bitmap bmp) { picDesktop.UpdateImage(bmp, false); } private void FrmRemoteDesktop_Load(object sender, EventArgs e) { this.Text = WindowHelper.GetWindowTitle("Remote Desktop", _connectClient); OnResize(EventArgs.Empty); // trigger resize event to align controls _remoteDesktopHandler.RefreshDisplays(); } /// /// Updates the title with the current frames per second. /// /// The new frames per second. private void frameCounter_FrameUpdated(FrameUpdatedEventArgs e) { this.Text = string.Format("{0} - FPS: {1}", WindowHelper.GetWindowTitle("Remote Desktop", _connectClient), e.CurrentFramesPerSecond.ToString("0.00")); } private void FrmRemoteDesktop_FormClosing(object sender, FormClosingEventArgs e) { // all cleanup logic goes here UnsubscribeEvents(); if (_remoteDesktopHandler.IsStarted) StopStream(); UnregisterMessageHandler(); _remoteDesktopHandler.Dispose(); picDesktop.Image?.Dispose(); } private void FrmRemoteDesktop_Resize(object sender, EventArgs e) { if (WindowState == FormWindowState.Minimized) return; _remoteDesktopHandler.LocalResolution = picDesktop.Size; panelTop.Left = (this.Width - panelTop.Width) / 2; btnShow.Left = (this.Width - btnShow.Width) / 2; btnHide.Left = (panelTop.Width - btnHide.Width) / 2; } private void btnStart_Click(object sender, EventArgs e) { if (cbMonitors.Items.Count == 0) { MessageBox.Show("No remote display detected.\nPlease wait till the client sends a list with available displays.", "Starting failed", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } SubscribeEvents(); StartStream(); } private void btnStop_Click(object sender, EventArgs e) { UnsubscribeEvents(); StopStream(); } #region Remote Desktop Input private void picDesktop_MouseDown(object sender, MouseEventArgs e) { if (picDesktop.Image != null && _enableMouseInput && this.ContainsFocus) { MouseAction action = MouseAction.None; if (e.Button == MouseButtons.Left) action = MouseAction.LeftDown; if (e.Button == MouseButtons.Right) action = MouseAction.RightDown; int selectedDisplayIndex = cbMonitors.SelectedIndex; _remoteDesktopHandler.SendMouseEvent(action, true, e.X, e.Y, selectedDisplayIndex); } } private void picDesktop_MouseUp(object sender, MouseEventArgs e) { if (picDesktop.Image != null && _enableMouseInput && this.ContainsFocus) { MouseAction action = MouseAction.None; if (e.Button == MouseButtons.Left) action = MouseAction.LeftUp; if (e.Button == MouseButtons.Right) action = MouseAction.RightUp; int selectedDisplayIndex = cbMonitors.SelectedIndex; _remoteDesktopHandler.SendMouseEvent(action, false, e.X, e.Y, selectedDisplayIndex); } } private void picDesktop_MouseMove(object sender, MouseEventArgs e) { if (picDesktop.Image != null && _enableMouseInput && this.ContainsFocus) { int selectedDisplayIndex = cbMonitors.SelectedIndex; _remoteDesktopHandler.SendMouseEvent(MouseAction.MoveCursor, false, e.X, e.Y, selectedDisplayIndex); } } private void OnMouseWheelMove(object sender, MouseEventArgs e) { if (picDesktop.Image != null && _enableMouseInput && this.ContainsFocus) { _remoteDesktopHandler.SendMouseEvent(e.Delta == 120 ? MouseAction.ScrollUp : MouseAction.ScrollDown, false, 0, 0, cbMonitors.SelectedIndex); } } private void OnKeyDown(object sender, KeyEventArgs e) { if (picDesktop.Image != null && _enableKeyboardInput && this.ContainsFocus) { if (!IsLockKey(e.KeyCode)) e.Handled = true; if (_keysPressed.Contains(e.KeyCode)) return; _keysPressed.Add(e.KeyCode); _remoteDesktopHandler.SendKeyboardEvent((byte)e.KeyCode, true); } } private void OnKeyUp(object sender, KeyEventArgs e) { if (picDesktop.Image != null && _enableKeyboardInput && this.ContainsFocus) { if (!IsLockKey(e.KeyCode)) e.Handled = true; _keysPressed.Remove(e.KeyCode); _remoteDesktopHandler.SendKeyboardEvent((byte)e.KeyCode, false); } } private bool IsLockKey(Keys key) { return ((key & Keys.CapsLock) == Keys.CapsLock) || ((key & Keys.NumLock) == Keys.NumLock) || ((key & Keys.Scroll) == Keys.Scroll); } #endregion #region Remote Desktop Configuration private void barQuality_Scroll(object sender, EventArgs e) { int value = barQuality.Value; lblQualityShow.Text = value.ToString(); if (value < 25) lblQualityShow.Text += " (low)"; else if (value >= 85) lblQualityShow.Text += " (best)"; else if (value >= 75) lblQualityShow.Text += " (high)"; else if (value >= 25) lblQualityShow.Text += " (mid)"; this.ActiveControl = picDesktop; } private void btnMouse_Click(object sender, EventArgs e) { if (_enableMouseInput) { this.picDesktop.Cursor = Cursors.Default; btnMouse.Image = Properties.Resources.mouse_delete; toolTipButtons.SetToolTip(btnMouse, "Enable mouse input."); _enableMouseInput = false; } else { this.picDesktop.Cursor = Cursors.Hand; btnMouse.Image = Properties.Resources.mouse_add; toolTipButtons.SetToolTip(btnMouse, "Disable mouse input."); _enableMouseInput = true; } this.ActiveControl = picDesktop; } private void btnKeyboard_Click(object sender, EventArgs e) { if (_enableKeyboardInput) { this.picDesktop.Cursor = Cursors.Default; btnKeyboard.Image = Properties.Resources.keyboard_delete; toolTipButtons.SetToolTip(btnKeyboard, "Enable keyboard input."); _enableKeyboardInput = false; } else { this.picDesktop.Cursor = Cursors.Hand; btnKeyboard.Image = Properties.Resources.keyboard_add; toolTipButtons.SetToolTip(btnKeyboard, "Disable keyboard input."); _enableKeyboardInput = true; } this.ActiveControl = picDesktop; } #endregion private void btnHide_Click(object sender, EventArgs e) { TogglePanelVisibility(false); } private void btnShow_Click(object sender, EventArgs e) { TogglePanelVisibility(true); } } }