900 lines
32 KiB
C#
900 lines
32 KiB
C#
using Gma.System.MouseKeyHook;
|
|
using Pulsar.Common.Enums;
|
|
using Pulsar.Common.Messages;
|
|
using Pulsar.Common.Messages.Monitoring.Clipboard;
|
|
using Pulsar.Server.Forms.DarkMode;
|
|
using Pulsar.Server.Forms.RemoteDesktopPopUp;
|
|
using Pulsar.Server.Helper;
|
|
using Pulsar.Server.Messages;
|
|
using Pulsar.Server.Networking;
|
|
using Pulsar.Server.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
|
|
namespace Pulsar.Server.Forms
|
|
{
|
|
public partial class FrmRemoteDesktop : Form
|
|
{
|
|
/// <summary>
|
|
/// States whether remote mouse input is enabled.
|
|
/// </summary>
|
|
private bool _enableMouseInput;
|
|
|
|
/// <summary>
|
|
/// States whether remote keyboard input is enabled.
|
|
/// </summary>
|
|
private bool _enableKeyboardInput;
|
|
|
|
/// <summary>
|
|
/// States whether drawing mode is enabled.
|
|
/// </summary>
|
|
private bool _enableDrawingMode;
|
|
|
|
/// <summary>
|
|
/// States whether eraser mode is enabled.
|
|
/// </summary>
|
|
private bool _enableEraserMode;
|
|
|
|
/// <summary>
|
|
/// The current stroke width for drawing.
|
|
/// </summary>
|
|
private int _strokeWidth = 5;
|
|
|
|
/// <summary>
|
|
/// The current drawing color.
|
|
/// </summary>
|
|
private Color _drawingColor = Color.Red;
|
|
|
|
/// <summary>
|
|
/// Keeps track of the previous mouse position for drawing.
|
|
/// </summary>
|
|
private Point _previousMousePosition = Point.Empty;
|
|
|
|
/// <summary>
|
|
/// Holds the state of the local keyboard hooks.
|
|
/// </summary>
|
|
private IKeyboardMouseEvents _keyboardHook;
|
|
|
|
/// <summary>
|
|
/// Holds the state of the local mouse hooks.
|
|
/// </summary>
|
|
private IKeyboardMouseEvents _mouseHook;
|
|
|
|
/// <summary>
|
|
/// A list of pressed keys for synchronization between key down & -up events.
|
|
/// </summary>
|
|
private readonly List<Keys> _keysPressed;
|
|
|
|
/// <summary>
|
|
/// Monitors clipboard changes on the server to send to the client
|
|
/// </summary>
|
|
private ClipboardMonitor _clipboardMonitor;
|
|
|
|
/// <summary>
|
|
/// States whether bidirectional clipboard sync is enabled
|
|
/// </summary>
|
|
private bool _enableBidirectionalClipboard;
|
|
|
|
/// <summary>
|
|
/// The client which can be used for the remote desktop.
|
|
/// </summary>
|
|
private readonly Client _connectClient;
|
|
|
|
/// <summary>
|
|
/// The message handler for handling the communication with the client.
|
|
/// </summary>
|
|
private readonly RemoteDesktopHandler _remoteDesktopHandler;
|
|
|
|
/// <summary>
|
|
/// Holds the opened remote desktop form for each client.
|
|
/// </summary>
|
|
private static readonly Dictionary<Client, FrmRemoteDesktop> OpenedForms = new Dictionary<Client, FrmRemoteDesktop>();
|
|
|
|
private bool _useGPU = false;
|
|
|
|
private int _sizeFrames = 0; // independent counter for size label updates
|
|
|
|
/// <summary>
|
|
/// Creates a new remote desktop form for the client or gets the current open form, if there exists one already.
|
|
/// </summary>
|
|
/// <param name="client">The client used for the remote desktop form.</param>
|
|
/// <returns>
|
|
/// Returns a new remote desktop form for the client if there is none currently open, otherwise creates a new one.
|
|
/// </returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="FrmRemoteDesktop"/> class using the given client.
|
|
/// </summary>
|
|
/// <param name="client">The client used for the remote desktop form.</param>
|
|
public FrmRemoteDesktop(Client client)
|
|
{
|
|
_connectClient = client;
|
|
_remoteDesktopHandler = new RemoteDesktopHandler(client);
|
|
_keysPressed = new List<Keys>();
|
|
_clipboardMonitor = new ClipboardMonitor(client);
|
|
|
|
RegisterMessageHandler();
|
|
try
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"FrmRemoteDesktop.InitializeComponent failed: {ex}");
|
|
MessageBox.Show(
|
|
$"Failed to initialize Remote Desktop form.\n\n{ex}",
|
|
"Remote Desktop Error",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Error);
|
|
throw;
|
|
}
|
|
|
|
DarkModeManager.ApplyDarkMode(this);
|
|
ScreenCaptureHider.ScreenCaptureHider.Apply(this.Handle);
|
|
|
|
colorPicker.BackColor = _drawingColor;
|
|
colorPicker.FlatStyle = FlatStyle.Flat;
|
|
colorPicker.FlatAppearance.BorderColor = Color.White;
|
|
colorPicker.Text = "Color";
|
|
colorPicker.ForeColor = Color.White;
|
|
|
|
ConfigureDrawingButtons();
|
|
|
|
btnShowDrawingTools.Enabled = false;
|
|
|
|
strokeWidthTrackBar.Value = _strokeWidth;
|
|
strokeWidthTrackBar.ValueChanged += strokeWidthTrackBar_ValueChanged;
|
|
colorPicker.Click += colorPicker_Click;
|
|
|
|
// Update tooltip text for bidirectional clipboard
|
|
toolTipButtons.SetToolTip(this.btnBiDirectionalClipboard, "Enable bidirectional clipboard sync");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configures the drawing tool buttons with the correct styling and icons
|
|
/// </summary>
|
|
private void ConfigureDrawingButtons()
|
|
{
|
|
// pencil
|
|
btnDrawing.Size = new Size(60, 28);
|
|
btnDrawing.FlatStyle = FlatStyle.Flat;
|
|
btnDrawing.FlatAppearance.BorderSize = 1;
|
|
btnDrawing.BackgroundImage = Properties.Resources.pencil;
|
|
btnDrawing.BackgroundImageLayout = ImageLayout.Zoom;
|
|
btnDrawing.Text = string.Empty;
|
|
btnDrawing.BackColor = SystemColors.Control;
|
|
btnDrawing.UseVisualStyleBackColor = false;
|
|
toolTipButtons.SetToolTip(btnDrawing, "Enable drawing");
|
|
|
|
// eraser
|
|
btnEraser.Size = new Size(60, 28);
|
|
btnEraser.FlatStyle = FlatStyle.Flat;
|
|
btnEraser.FlatAppearance.BorderSize = 1;
|
|
btnEraser.BackgroundImage = Properties.Resources.eraser;
|
|
btnEraser.BackgroundImageLayout = ImageLayout.Zoom;
|
|
btnEraser.Text = string.Empty;
|
|
btnEraser.BackColor = SystemColors.Control;
|
|
btnEraser.UseVisualStyleBackColor = false;
|
|
toolTipButtons.SetToolTip(btnEraser, "Enable eraser");
|
|
|
|
// clear
|
|
btnClearDrawing.Size = new Size(60, 28);
|
|
btnClearDrawing.FlatStyle = FlatStyle.Flat;
|
|
btnClearDrawing.FlatAppearance.BorderSize = 1;
|
|
btnClearDrawing.BackgroundImage = Properties.Resources.clear;
|
|
btnClearDrawing.BackgroundImageLayout = ImageLayout.Zoom;
|
|
btnClearDrawing.Text = string.Empty;
|
|
btnClearDrawing.BackColor = SystemColors.Control;
|
|
btnClearDrawing.UseVisualStyleBackColor = false;
|
|
toolTipButtons.SetToolTip(btnClearDrawing, "Clear drawing");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called whenever a client disconnects.
|
|
/// </summary>
|
|
/// <param name="client">The client which disconnected.</param>
|
|
/// <param name="connected">True if the client connected, false if disconnected</param>
|
|
private void ClientDisconnected(Client client, bool connected)
|
|
{
|
|
if (!connected)
|
|
{
|
|
this.Invoke((MethodInvoker)this.Close);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the remote desktop message handler for client communication.
|
|
/// </summary>
|
|
private void RegisterMessageHandler()
|
|
{
|
|
_connectClient.ClientState += ClientDisconnected;
|
|
_remoteDesktopHandler.DisplaysChanged += DisplaysChanged;
|
|
_remoteDesktopHandler.ProgressChanged += UpdateImage;
|
|
MessageHandler.Register(_remoteDesktopHandler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters the remote desktop message handler.
|
|
/// </summary>
|
|
private void UnregisterMessageHandler()
|
|
{
|
|
MessageHandler.Unregister(_remoteDesktopHandler);
|
|
_remoteDesktopHandler.DisplaysChanged -= DisplaysChanged;
|
|
_remoteDesktopHandler.ProgressChanged -= UpdateImage;
|
|
_connectClient.ClientState -= ClientDisconnected;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to local mouse and keyboard events for remote desktop input.
|
|
/// </summary>
|
|
private void SubscribeEvents()
|
|
{
|
|
_keyboardHook = Hook.GlobalEvents();
|
|
_keyboardHook.KeyDown += OnKeyDown;
|
|
_keyboardHook.KeyUp += OnKeyUp;
|
|
|
|
_mouseHook = Hook.AppEvents();
|
|
_mouseHook.MouseWheel += OnMouseWheelMove;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes from local mouse and keyboard events.
|
|
/// </summary>
|
|
private void UnsubscribeEvents()
|
|
{
|
|
if (_keyboardHook != null)
|
|
{
|
|
_keyboardHook.KeyDown -= OnKeyDown;
|
|
_keyboardHook.KeyUp -= OnKeyUp;
|
|
_keyboardHook.Dispose();
|
|
}
|
|
if (_mouseHook != null)
|
|
{
|
|
_mouseHook.MouseWheel -= OnMouseWheelMove;
|
|
_mouseHook.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the remote desktop stream and begin to receive desktop frames.
|
|
/// </summary>
|
|
private void StartStream(bool useGpu)
|
|
{
|
|
ToggleConfigurationControls(true);
|
|
|
|
picDesktop.Start();
|
|
// Subscribe to the new frame counter.
|
|
picDesktop.SetFrameUpdatedEvent(frameCounter_FrameUpdated);
|
|
|
|
this.ActiveControl = picDesktop;
|
|
|
|
_remoteDesktopHandler.BeginReceiveFrames(barQuality.Value, cbMonitors.SelectedIndex, useGpu);
|
|
|
|
btnShowDrawingTools.Enabled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the remote desktop stream.
|
|
/// </summary>
|
|
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();
|
|
|
|
btnShowDrawingTools.Enabled = false;
|
|
|
|
if (panelDrawingTools.Visible)
|
|
{
|
|
panelDrawingTools.Visible = false;
|
|
btnShowDrawingTools.Image = Properties.Resources.arrow_up;
|
|
toolTipButtons.SetToolTip(btnShowDrawingTools, "Show drawing tools");
|
|
}
|
|
|
|
_enableDrawingMode = false;
|
|
_enableEraserMode = false;
|
|
|
|
ConfigureDrawingButtons();
|
|
|
|
picDesktop.Cursor = Cursors.Default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles the activatability of configuration controls in the status/configuration panel.
|
|
/// </summary>
|
|
/// <param name="started">When set to <code>true</code> the configuration controls get enabled, otherwise they get disabled.</param>
|
|
private void ToggleConfigurationControls(bool started)
|
|
{
|
|
btnStart.Enabled = !started;
|
|
btnStop.Enabled = started;
|
|
barQuality.Enabled = !started;
|
|
cbMonitors.Enabled = !started;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Toggles the visibility of the status/configuration panel.
|
|
/// </summary>
|
|
/// <param name="visible">Decides if the panel should be visible.</param>
|
|
private void TogglePanelVisibility(bool visible)
|
|
{
|
|
panelTop.Visible = visible;
|
|
btnShow.Visible = !visible;
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called whenever the remote displays changed.
|
|
/// </summary>
|
|
/// <param name="sender">The message handler which raised the event.</param>
|
|
/// <param name="displays">The currently available displays.</param>
|
|
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;
|
|
}
|
|
|
|
private void UpdateImage(object sender, Bitmap bmp)
|
|
{
|
|
_sizeFrames++;
|
|
if (_sizeFrames >= 60)
|
|
{
|
|
_sizeFrames = 0;
|
|
long last = _remoteDesktopHandler.LastFrameSizeBytes;
|
|
double avg = _remoteDesktopHandler.AverageFrameSizeBytes;
|
|
if (last > 0 || avg > 0)
|
|
{
|
|
double lastKB = last / 1024.0;
|
|
double avgKB = avg / 1024.0;
|
|
this.Invoke((MethodInvoker)delegate
|
|
{
|
|
sizeLabelCounter.Text = $"{avgKB:0.0} KB";
|
|
});
|
|
}
|
|
}
|
|
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
|
|
|
|
panelDrawingTools.Visible = false;
|
|
btnShowDrawingTools.Image = Properties.Resources.arrow_up;
|
|
toolTipButtons.SetToolTip(btnShowDrawingTools, "Show drawing tools");
|
|
|
|
_enableDrawingMode = false;
|
|
_enableEraserMode = false;
|
|
|
|
ConfigureDrawingButtons();
|
|
|
|
_remoteDesktopHandler.RefreshDisplays();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the title with the current frames per second.
|
|
/// </summary>
|
|
/// <param name="e">The new frames per second.</param>
|
|
private void frameCounter_FrameUpdated(FrameUpdatedEventArgs e)
|
|
{
|
|
float fpsToShow = _remoteDesktopHandler.CurrentFps > 0 ? _remoteDesktopHandler.CurrentFps : e.CurrentFramesPerSecond;
|
|
this.Text = string.Format("{0} - FPS: {1}", WindowHelper.GetWindowTitle("Remote Desktop", _connectClient), fpsToShow.ToString("0.00"));
|
|
}
|
|
|
|
private void FrmRemoteDesktop_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
// all cleanup logic goes here
|
|
SetClientClipboardSync(enabled: false);
|
|
UnsubscribeEvents();
|
|
if (_remoteDesktopHandler.IsStarted) StopStream();
|
|
UnregisterMessageHandler();
|
|
_remoteDesktopHandler.Dispose();
|
|
_clipboardMonitor?.Dispose();
|
|
|
|
}
|
|
|
|
private void FrmRemoteDesktop_Resize(object sender, EventArgs e)
|
|
{
|
|
if (WindowState == FormWindowState.Minimized)
|
|
return;
|
|
|
|
_remoteDesktopHandler.LocalResolution = picDesktop.ClientSize;
|
|
btnShow.Left = (this.Width - btnShow.Width) / 2;
|
|
btnShow.Top = this.Height - btnShow.Height - 40;
|
|
}
|
|
|
|
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(_useGPU);
|
|
}
|
|
|
|
private void btnStop_Click(object sender, EventArgs e)
|
|
{
|
|
UnsubscribeEvents();
|
|
StopStream();
|
|
}
|
|
|
|
#region Remote Desktop Input
|
|
|
|
private void picDesktop_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if (this.ContainsFocus)
|
|
{
|
|
if ((_enableDrawingMode || _enableEraserMode) && e.Button == MouseButtons.Left)
|
|
{
|
|
_previousMousePosition = e.Location;
|
|
}
|
|
else if (_enableMouseInput && !(_enableDrawingMode || _enableEraserMode))
|
|
{
|
|
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_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (this.ContainsFocus)
|
|
{
|
|
if ((_enableDrawingMode || _enableEraserMode) && e.Button == MouseButtons.Left)
|
|
{
|
|
if (_previousMousePosition != Point.Empty &&
|
|
(_previousMousePosition.X != e.X || _previousMousePosition.Y != e.Y))
|
|
{
|
|
int selectedDisplayIndex = cbMonitors.SelectedIndex;
|
|
|
|
bool useEraser = _enableEraserMode;
|
|
|
|
_remoteDesktopHandler.SendDrawingEvent(
|
|
e.X, e.Y,
|
|
_previousMousePosition.X, _previousMousePosition.Y,
|
|
_strokeWidth,
|
|
_drawingColor.ToArgb(),
|
|
useEraser,
|
|
false,
|
|
selectedDisplayIndex);
|
|
|
|
_previousMousePosition = e.Location;
|
|
}
|
|
}
|
|
else if (_enableMouseInput && !(_enableDrawingMode || _enableEraserMode))
|
|
{
|
|
int selectedDisplayIndex = cbMonitors.SelectedIndex;
|
|
|
|
_remoteDesktopHandler.SendMouseEvent(MouseAction.MoveCursor, false, e.X, e.Y, selectedDisplayIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void picDesktop_MouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
if (this.ContainsFocus)
|
|
{
|
|
if ((_enableDrawingMode || _enableEraserMode) && e.Button == MouseButtons.Left)
|
|
{
|
|
_previousMousePosition = Point.Empty;
|
|
}
|
|
else if (_enableMouseInput && !(_enableDrawingMode || _enableEraserMode))
|
|
{
|
|
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 OnMouseWheelMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (_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.HasFrame && _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.HasFrame && _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 Remote Desktop Input
|
|
|
|
#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;
|
|
}
|
|
|
|
UpdateInputButtonsVisualState();
|
|
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;
|
|
}
|
|
|
|
UpdateInputButtonsVisualState();
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void enableGPU_Click(object sender, EventArgs e)
|
|
{
|
|
_useGPU = !_useGPU;
|
|
if (_useGPU)
|
|
{
|
|
enableGPU.Image = Properties.Resources.computer_go; // enable GPU
|
|
toolTipButtons.SetToolTip(enableGPU, "Disable GPU.");
|
|
}
|
|
else
|
|
{
|
|
enableGPU.Image = Properties.Resources.computer_error; // disable GPU
|
|
toolTipButtons.SetToolTip(enableGPU, "Enable GPU.");
|
|
}
|
|
UpdateInputButtonsVisualState();
|
|
}
|
|
|
|
private void btnDrawing_Click(object sender, EventArgs e)
|
|
{
|
|
if (!_remoteDesktopHandler.IsStarted)
|
|
{
|
|
MessageBox.Show("Drawing is only available when Remote Desktop is started",
|
|
"Drawing unavailable", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
_enableDrawingMode = !_enableDrawingMode;
|
|
|
|
if (_enableDrawingMode)
|
|
{
|
|
_enableEraserMode = false;
|
|
|
|
btnEraser.BackColor = SystemColors.Control;
|
|
toolTipButtons.SetToolTip(btnEraser, "Enable eraser");
|
|
|
|
btnDrawing.BackColor = Color.FromArgb(120, 170, 120);
|
|
toolTipButtons.SetToolTip(btnDrawing, "Disable drawing");
|
|
picDesktop.Cursor = Cursors.Cross;
|
|
}
|
|
else
|
|
{
|
|
btnDrawing.BackColor = SystemColors.Control;
|
|
toolTipButtons.SetToolTip(btnDrawing, "Enable drawing");
|
|
picDesktop.Cursor = Cursors.Default;
|
|
}
|
|
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void btnEraser_Click(object sender, EventArgs e)
|
|
{
|
|
if (!_remoteDesktopHandler.IsStarted)
|
|
{
|
|
MessageBox.Show("Eraser is only available when Remote Desktop is started",
|
|
"Eraser unavailable", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
_enableEraserMode = !_enableEraserMode;
|
|
|
|
if (_enableEraserMode)
|
|
{
|
|
_enableDrawingMode = false;
|
|
|
|
btnDrawing.BackColor = SystemColors.Control;
|
|
toolTipButtons.SetToolTip(btnDrawing, "Enable drawing");
|
|
|
|
btnEraser.BackColor = Color.FromArgb(120, 170, 120);
|
|
toolTipButtons.SetToolTip(btnEraser, "Disable eraser");
|
|
picDesktop.Cursor = Cursors.Hand;
|
|
}
|
|
else
|
|
{
|
|
btnEraser.BackColor = SystemColors.Control;
|
|
toolTipButtons.SetToolTip(btnEraser, "Enable eraser");
|
|
picDesktop.Cursor = Cursors.Default;
|
|
}
|
|
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void btnShowDrawingTools_Click(object sender, EventArgs e)
|
|
{
|
|
ToggleDrawingPanelVisibility(!panelDrawingTools.Visible);
|
|
}
|
|
|
|
private void ToggleDrawingPanelVisibility(bool visible)
|
|
{
|
|
if (visible && !_remoteDesktopHandler.IsStarted)
|
|
{
|
|
MessageBox.Show("Drawing tools are only available when Remote Desktop is started",
|
|
"Drawing unavailable", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
panelDrawingTools.Visible = visible;
|
|
btnShowDrawingTools.Image = visible ?
|
|
Properties.Resources.arrow_down :
|
|
Properties.Resources.arrow_up;
|
|
toolTipButtons.SetToolTip(btnShowDrawingTools,
|
|
visible ? "Hide drawing tools" : "Show drawing tools");
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void btnClearDrawing_Click(object sender, EventArgs e)
|
|
{
|
|
if (!_remoteDesktopHandler.IsStarted)
|
|
{
|
|
MessageBox.Show("Clear drawing is only available when Remote Desktop is started",
|
|
"Clear unavailable", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
int displayIndex = cbMonitors.SelectedIndex;
|
|
_remoteDesktopHandler.SendDrawingEvent(0, 0, 0, 0, 0, 0, false, true, displayIndex);
|
|
}
|
|
|
|
private void colorPicker_Click(object sender, EventArgs e)
|
|
{
|
|
if (!_remoteDesktopHandler.IsStarted)
|
|
{
|
|
MessageBox.Show("Color selection is only available when Remote Desktop is started",
|
|
"Color selection unavailable", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
|
return;
|
|
}
|
|
|
|
ColorDialog colorDialog = new ColorDialog();
|
|
colorDialog.Color = _drawingColor;
|
|
colorDialog.AllowFullOpen = true;
|
|
colorDialog.AnyColor = true;
|
|
|
|
if (colorDialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
_drawingColor = colorDialog.Color;
|
|
colorPicker.BackColor = _drawingColor;
|
|
|
|
colorPicker.ForeColor = GetContrastColor(_drawingColor);
|
|
}
|
|
}
|
|
|
|
private void strokeWidthTrackBar_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
_strokeWidth = strokeWidthTrackBar.Value;
|
|
}
|
|
|
|
private Color GetContrastColor(Color color)
|
|
{
|
|
int brightness = (int)Math.Sqrt(
|
|
color.R * color.R * 0.299 +
|
|
color.G * color.G * 0.587 +
|
|
color.B * color.B * 0.114);
|
|
|
|
return brightness > 130 ? Color.Black : Color.White;
|
|
}
|
|
|
|
#endregion Remote Desktop Configuration
|
|
|
|
/// <summary>
|
|
/// Updates the visual state of the input buttons based on current input settings
|
|
/// </summary>
|
|
private void UpdateInputButtonsVisualState()
|
|
{
|
|
UpdateButtonState(btnMouse, _enableMouseInput);
|
|
UpdateButtonState(btnKeyboard, _enableKeyboardInput);
|
|
UpdateButtonState(btnBiDirectionalClipboard, _enableBidirectionalClipboard);
|
|
UpdateButtonState(enableGPU, _useGPU);
|
|
}
|
|
|
|
private void UpdateButtonState(Button button, bool enabled)
|
|
{
|
|
if (enabled)
|
|
{
|
|
button.BackColor = Color.FromArgb(0, 120, 0); // Dark green
|
|
button.FlatAppearance.BorderColor = Color.LimeGreen;
|
|
}
|
|
else
|
|
{
|
|
button.BackColor = Color.FromArgb(40, 40, 40); // Default dark
|
|
button.FlatAppearance.BorderColor = Color.Gray;
|
|
}
|
|
}
|
|
|
|
private void btnHide_Click(object sender, EventArgs e)
|
|
{
|
|
TogglePanelVisibility(false);
|
|
}
|
|
|
|
private void btnShow_Click(object sender, EventArgs e)
|
|
{
|
|
TogglePanelVisibility(true);
|
|
}
|
|
|
|
private void btnStartProgramOnDisplay_Click(object sender, EventArgs e)
|
|
{
|
|
int currentDisplayIndex = cbMonitors.SelectedIndex;
|
|
FrmOpenApplicationOnMonitor frm = new FrmOpenApplicationOnMonitor(_connectClient, currentDisplayIndex);
|
|
frm.ShowDialog(this);
|
|
}
|
|
|
|
private void btnBiDirectionalClipboard_Click(object sender, EventArgs e)
|
|
{
|
|
_enableBidirectionalClipboard = !_enableBidirectionalClipboard;
|
|
|
|
UpdateInputButtonsVisualState();
|
|
|
|
// Server-side clipboard monitor only controls server->client sync
|
|
_clipboardMonitor.IsEnabled = _enableBidirectionalClipboard;
|
|
Debug.WriteLine(_clipboardMonitor.IsEnabled ? "Server->Client clipboard sync enabled." : "Server->Client clipboard sync disabled.");
|
|
|
|
SetClientClipboardSync(_enableBidirectionalClipboard);
|
|
|
|
if (_enableBidirectionalClipboard)
|
|
{
|
|
Thread clipboardThread = new Thread(() =>
|
|
{
|
|
try
|
|
{
|
|
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
|
|
|
|
if (Clipboard.ContainsText())
|
|
{
|
|
string clipboardText = Clipboard.GetText();
|
|
if (!string.IsNullOrEmpty(clipboardText))
|
|
{
|
|
Debug.WriteLine($"Sending initial clipboard: {clipboardText.Substring(0, Math.Min(20, clipboardText.Length))}...");
|
|
_connectClient.Send(new SendClipboardData { ClipboardText = clipboardText });
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error sending initial clipboard: {ex.Message}");
|
|
}
|
|
});
|
|
clipboardThread.SetApartmentState(ApartmentState.STA);
|
|
clipboardThread.Start();
|
|
}
|
|
}
|
|
|
|
private void SetClientClipboardSync(bool enabled)
|
|
{
|
|
try
|
|
{
|
|
if (_connectClient != null)
|
|
{
|
|
_connectClient.ClipboardSyncEnabled = enabled;
|
|
}
|
|
|
|
_connectClient?.Send(new SetClipboardMonitoringEnabled { Enabled = enabled });
|
|
Debug.WriteLine(enabled
|
|
? "Requested client clipboard sync enable."
|
|
: "Requested client clipboard sync disable.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Failed to update client clipboard sync state: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
} |