Files
i2p 773d05f8f1
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run
initial commit
2026-08-27 10:57:58 -06:00

274 lines
9.2 KiB
C#

using Pulsar.Common.Enums;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.Other;
using Pulsar.Common.Messages.Preview;
using Pulsar.Common.Messages.Webcam;
using Pulsar.Common.Networking;
using Pulsar.Common.Video.Codecs;
using Pulsar.Server.Controls;
using Pulsar.Server.Networking;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace Pulsar.Server.Messages
{
public class PreviewHandler : MessageProcessorBase<Bitmap>, IDisposable
{
public bool IsStarted { get; set; }
private readonly object _syncLock = new object();
private readonly PictureBox _box;
private readonly Client _client;
private UnsafeStreamCodec _codec;
private ListView _verticleStatsTable;
/// <summary>
/// Used in lock statements to synchronize access to <see cref="LocalResolution"/> between UI thread and thread pool.
/// </summary>
private readonly object _sizeLock = new object();
private int _lastPingMs = -1;
private GetPreviewResponse _lastPreviewResponse;
/// <summary>
/// The ping handler for measuring network latency separately from preview requests.
/// </summary>
private readonly PingHandler _pingHandler;public PreviewHandler(Client client, PictureBox box, ListView importantStatsView) : base(true)
{
_box = box;
_client = client;
LocalResolution = box.Size;
_verticleStatsTable = importantStatsView;
_pingHandler = new PingHandler(client);
_pingHandler.ProgressChanged += OnPingReceived;
MessageHandler.Register(_pingHandler);
try
{
_pingHandler.SendPing();
}
catch (Exception ex)
{
Debug.WriteLine($"Error sending initial ping: {ex.Message}");
}
}
/// <summary>
/// Called when a ping response is received.
/// </summary>
/// <param name="sender">The ping handler.</param>
/// <param name="pingMs">The ping time in milliseconds.</param>
private void OnPingReceived(object sender, int pingMs)
{
SetLastPing(pingMs);
}
/// <summary>
/// The local resolution, see <seealso cref="LocalResolution"/>.
/// </summary>
private Size _localResolution;
/// <summary>
/// The local resolution in width x height. It indicates to which resolution the received frame should be resized.
/// </summary>
/// <remarks>
/// This property is thread-safe.
/// </remarks>
public Size LocalResolution
{
get
{
lock (_sizeLock)
{
return _localResolution;
}
}
set
{
lock (_sizeLock)
{
_localResolution = value;
}
}
}
public override bool CanExecute(IMessage message) => message is GetPreviewResponse;
public override bool CanExecuteFrom(ISender sender) => _client.Equals(sender);
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetPreviewResponse frame:
Execute(sender, frame);
break;
}
}
private void Execute(ISender client, GetPreviewResponse message)
{
lock (_syncLock)
{
if (_codec == null || _codec.ImageQuality != message.Quality || _codec.Monitor != message.Monitor || _codec.Resolution != message.Resolution)
{
_codec?.Dispose();
_codec = new UnsafeStreamCodec(message.Quality, message.Monitor, message.Resolution);
}
using (MemoryStream ms = new MemoryStream(message.Image))
{
try
{
Bitmap boxmap = new Bitmap(_codec.DecodeData(ms), LocalResolution);
OnReport(boxmap);
_box.Image = boxmap;
}
catch (Exception ex)
{
Debug.WriteLine("Error decoding image: " + ex.Message);
}
}
message.Image = null;
if (_verticleStatsTable.InvokeRequired)
{
_verticleStatsTable.Invoke(new MethodInvoker(() =>
{
UpdateStats(message);
}));
}
else
{
UpdateStats(message);
}
}
}
public void SetLastPing(int ms)
{
_lastPingMs = ms;
if (_verticleStatsTable != null && _verticleStatsTable.IsHandleCreated && _lastPreviewResponse != null)
{
if (_verticleStatsTable.InvokeRequired)
{
_verticleStatsTable.Invoke(new MethodInvoker(() => UpdateStats(_lastPreviewResponse)));
}
else
{
UpdateStats(_lastPreviewResponse);
}
}
}
private void UpdateStats(GetPreviewResponse message)
{
try
{
if (message == null)
{
return;
}
_lastPreviewResponse = message;
// Check if the ListView has been initialized and has columns
if (_verticleStatsTable.Columns.Count < 2)
{
// Create columns if they don't exist
if (_verticleStatsTable.Columns.Count == 0)
_verticleStatsTable.Columns.Add("Names", 100);
if (_verticleStatsTable.Columns.Count == 1)
_verticleStatsTable.Columns.Add("Stats", 150);
}
// Clear existing items to avoid duplicate entries
_verticleStatsTable.Items.Clear();
// Add the stats as new items
var cpuItem = new ListViewItem("CPU");
cpuItem.SubItems.Add(message.CPU);
_verticleStatsTable.Items.Add(cpuItem);
var gpuItem = new ListViewItem("GPU");
gpuItem.SubItems.Add(message.GPU);
_verticleStatsTable.Items.Add(gpuItem);
if (double.TryParse(message.RAM, out double ramInMb))
{
double ramInGb = ramInMb / 1024;
int roundedRAM = (int)Math.Round(ramInGb);
var ramItem = new ListViewItem("RAM");
ramItem.SubItems.Add($"{roundedRAM} GB");
_verticleStatsTable.Items.Add(ramItem);
}
else
{
var ramItem = new ListViewItem("RAM");
ramItem.SubItems.Add(message.RAM);
_verticleStatsTable.Items.Add(ramItem);
}
var uptimeItem = new ListViewItem("Uptime");
uptimeItem.SubItems.Add(message.Uptime);
_verticleStatsTable.Items.Add(uptimeItem);
var antivirusItem = new ListViewItem("Antivirus");
antivirusItem.SubItems.Add(message.AV);
_verticleStatsTable.Items.Add(antivirusItem);
var mainBrowserItem = new ListViewItem("Default Browser");
mainBrowserItem.SubItems.Add(message.MainBrowser);
_verticleStatsTable.Items.Add(mainBrowserItem);
var pingItem = new ListViewItem("Ping");
pingItem.SubItems.Add(_lastPingMs >= 0 ? _lastPingMs + " ms" : "N/A");
_verticleStatsTable.Items.Add(pingItem);
var webcamItem = new ListViewItem("Webcam");
webcamItem.SubItems.Add(message.HasWebcam ? "Yes" : "No");
_verticleStatsTable.Items.Add(webcamItem);
var afkItem = new ListViewItem("AFK Time");
afkItem.SubItems.Add(message.AFKTime);
_verticleStatsTable.Items.Add(afkItem);
}
catch (Exception ex)
{
Debug.WriteLine("Error updating stats: " + ex.Message);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_syncLock)
{
_codec?.Dispose();
IsStarted = false;
}
if (_pingHandler != null)
{
MessageHandler.Unregister(_pingHandler);
_pingHandler.ProgressChanged -= OnPingReceived;
}
}
}
}
}