using Quasar.Common.Enums;
using Quasar.Common.Messages;
using Quasar.Common.Networking;
using Quasar.Common.Video.Codecs;
using Quasar.Server.Networking;
using System;
using System.Drawing;
using System.IO;
namespace Quasar.Server.Messages
{
///
/// Handles messages for the interaction with the remote desktop.
///
public class RemoteDesktopHandler : MessageProcessorBase, IDisposable
{
///
/// States if the client is currently streaming desktop frames.
///
public bool IsStarted { get; set; }
///
/// Used in lock statements to synchronize access to between UI thread and thread pool.
///
private readonly object _syncLock = new object();
///
/// Used in lock statements to synchronize access to between UI thread and thread pool.
///
private readonly object _sizeLock = new object();
///
/// The local resolution, see .
///
private Size _localResolution;
///
/// The local resolution in width x height. It indicates to which resolution the received frame should be resized.
///
///
/// This property is thread-safe.
///
public Size LocalResolution
{
get
{
lock (_sizeLock)
{
return _localResolution;
}
}
set
{
lock (_sizeLock)
{
_localResolution = value;
}
}
}
///
/// Represents the method that will handle display changes.
///
/// The message processor which raised the event.
/// All currently available displays.
public delegate void DisplaysChangedEventHandler(object sender, int value);
///
/// Raised when a display changed.
///
///
/// Handlers registered with this event will be invoked on the
/// chosen when the instance was constructed.
///
public event DisplaysChangedEventHandler DisplaysChanged;
///
/// Reports changed displays.
///
/// All currently available displays.
private void OnDisplaysChanged(int value)
{
SynchronizationContext.Post(val =>
{
var handler = DisplaysChanged;
handler?.Invoke(this, (int)val);
}, value);
}
///
/// The client which is associated with this remote desktop handler.
///
private readonly Client _client;
///
/// The video stream codec used to decode received frames.
///
private UnsafeStreamCodec _codec;
///
/// Initializes a new instance of the class using the given client.
///
/// The associated client.
public RemoteDesktopHandler(Client client) : base(true)
{
_client = client;
}
///
public override bool CanExecute(IMessage message) => message is GetDesktopResponse || message is GetMonitorsResponse;
///
public override bool CanExecuteFrom(ISender sender) => _client.Equals(sender);
///
public override void Execute(ISender sender, IMessage message)
{
switch (message)
{
case GetDesktopResponse d:
Execute(sender, d);
break;
case GetMonitorsResponse m:
Execute(sender, m);
break;
}
}
///
/// Begins receiving frames from the client using the specified quality and display.
///
/// The quality of the remote desktop frames.
/// The display to receive frames from.
public void BeginReceiveFrames(int quality, int display)
{
lock (_syncLock)
{
IsStarted = true;
_codec?.Dispose();
_codec = null;
_client.Send(new GetDesktop { CreateNew = true, Quality = quality, DisplayIndex = display });
}
}
///
/// Ends receiving frames from the client.
///
public void EndReceiveFrames()
{
lock (_syncLock)
{
IsStarted = false;
}
}
///
/// Refreshes the available displays of the client.
///
public void RefreshDisplays()
{
_client.Send(new GetMonitors());
}
///
/// Sends a mouse event to the specified display of the client.
///
/// The mouse action to send.
/// Indicates whether it's a mousedown or mouseup event.
/// The X-coordinate inside the .
/// The Y-coordinate inside the .
/// The display to execute the mouse event on.
public void SendMouseEvent(MouseAction mouseAction, bool isMouseDown, int x, int y, int displayIndex)
{
lock (_syncLock)
{
_client.Send(new DoMouseEvent
{
Action = mouseAction,
IsMouseDown = isMouseDown,
// calculate remote width & height
X = x * _codec.Resolution.Width / LocalResolution.Width,
Y = y * _codec.Resolution.Height / LocalResolution.Height,
MonitorIndex = displayIndex
});
}
}
///
/// Sends a keyboard event to the client.
///
/// The pressed key.
/// Indicates whether it's a keydown or keyup event.
public void SendKeyboardEvent(byte keyCode, bool keyDown)
{
_client.Send(new DoKeyboardEvent {Key = keyCode, KeyDown = keyDown});
}
private void Execute(ISender client, GetDesktopResponse message)
{
lock (_syncLock)
{
if (!IsStarted)
return;
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))
{
// create deep copy & resize bitmap to local resolution
OnReport(new Bitmap(_codec.DecodeData(ms), LocalResolution));
}
message.Image = null;
client.Send(new GetDesktop {Quality = message.Quality, DisplayIndex = message.Monitor});
}
}
private void Execute(ISender client, GetMonitorsResponse message)
{
OnDisplaysChanged(message.Number);
}
///
/// Disposes all managed and unmanaged resources associated with this message processor.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
lock (_syncLock)
{
_codec?.Dispose();
IsStarted = false;
}
}
}
}
}