using System.Drawing; using System.IO; using Quasar.Common.Enums; using Quasar.Common.Messages; using Quasar.Common.Networking; using Quasar.Common.Video.Codecs; using Quasar.Server.Networking; namespace Quasar.Server.Messages { public class RemoteDesktopHandler : MessageProcessorBase { /// /// 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); } protected override void Dispose(bool disposing) { if (disposing) { lock (_syncLock) { _codec?.Dispose(); IsStarted = false; } } } } }