diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-03-04 21:32:42 +0200 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-03-04 21:32:42 +0200 |
| commit | f17d39f37cac50861467e07a7bee40534d20100a (patch) | |
| tree | 1d6ba97191b866eee6ffc6ef904fe3803a84decd /Software | |
| parent | 565e48de649d3d14e6b82012b6aa2e3819a3c82c (diff) | |
| download | Tango-f17d39f37cac50861467e07a7bee40534d20100a.tar.gz Tango-f17d39f37cac50861467e07a7bee40534d20100a.zip | |
Improved "Notify Continuous Requests About Disconnection".
Integrated FSE/PPC Remote Desktop.
Implemented RemoteDesktopService / RemoteDesktopProvider.
Implemented Mouse/Keyboard gestures.
Diffstat (limited to 'Software')
34 files changed, 1082 insertions, 180 deletions
diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs index eb5d57447..c8365af3b 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/ViewModels/RemoteDesktopViewVM.cs @@ -6,10 +6,13 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; using System.Windows.Media.Imaging; using Tango.Core.Commands; using Tango.Core.Threading; using Tango.FSE.Common; +using Tango.FSE.Common.RemoteDesktop; using Tango.RemoteDesktop.Frames; using Tango.RemoteDesktop.Network; @@ -17,8 +20,6 @@ namespace Tango.FSE.PPCConsole.ViewModels { public class RemoteDesktopViewVM : FSEViewModel { - private RasterFrame _currentFrame; - private BitmapSource _source; public BitmapSource Source { @@ -31,7 +32,7 @@ namespace Tango.FSE.PPCConsole.ViewModels public RemoteDesktopViewVM() { - StartCommand = new RelayCommand(StartRemoteDesktop, () => MachineProvider.IsConnected); + StartCommand = new RelayCommand(StartRemoteDesktop); StopCommand = new RelayCommand(StopRemoteDesktop); } @@ -39,71 +40,70 @@ namespace Tango.FSE.PPCConsole.ViewModels { base.OnApplicationStarted(); - MachineProvider.MachineConnected += (_, __) => InvalidateRelayCommands(); - MachineProvider.MachineDisconnected += (_, __) => InvalidateRelayCommands(); + RemoteDesktopProvider.FrameReceived += RemoteDesktopProvider_FrameReceived; } - private void StartRemoteDesktop() + private async void StartRemoteDesktop() { - SequencerThread<StartRemoteDesktopSessionResponse> sequencer = null; - sequencer = new SequencerThread<StartRemoteDesktopSessionResponse>((response) => + try + { + await RemoteDesktopProvider.StartSession(); + } + catch (Exception ex) { - if (response.Packet == null) - { - sequencer.FrameRate = 1000 / response.FrameRate; - return; //Returned just to notice that there was no timeout.. - } + await NotificationProvider.ShowError($"Error starting remote desktop session.\n{ex.FlattenMessage()}"); + } + } - try - { - if (!response.Packet.IsPartial) - { - using (MemoryStream ms = new MemoryStream(response.Packet.Bitmap)) - { - if (_currentFrame != null) - { - _currentFrame.Dispose(); - } + private async void StopRemoteDesktop() + { + try + { + await RemoteDesktopProvider.EndSession(); + } + catch (Exception ex) + { + await NotificationProvider.ShowError($"Error stopping remote desktop session.\n{ex.FlattenMessage()}"); + } + } - _currentFrame = new RasterFrame(new Bitmap(ms)); - } - } - else - { - using (MemoryStream ms = new MemoryStream(response.Packet.Bitmap)) - { - var diffFrame = new RasterFrame(new Bitmap(ms), response.Packet.PartialRegion.Left, response.Packet.PartialRegion.Top); - diffFrame.Apply(_currentFrame); - diffFrame.Dispose(); - } - } + private void RemoteDesktopProvider_FrameReceived(object sender, DesktopFrameReceivedEventArgs e) + { + Source = e.Source; + } - Source = _currentFrame.ToBitmapSource(); - } - catch (Exception ex) - { - Debug.WriteLine($"Error on remote desktop packet received: {ex.Message}"); - } - }); + #region Mouse & Keyboard Handlers From View - sequencer.Start(); + public void OnMouseDown(MouseButton changedButton, System.Windows.Point point, System.Windows.Size size) + { + RemoteDesktopProvider.MouseDown(changedButton, point, size); + } - MachineProvider.MachineOperator.SendGenericContinuousRequest<StartRemoteDesktopSessionRequest, StartRemoteDesktopSessionResponse>(new StartRemoteDesktopSessionRequest() { }, new Transport.TransportContinuousRequestConfig() - { - ContinuousTimeout = TimeSpan.FromSeconds(30), - }).Subscribe((response) => - { - sequencer.Push(response); + public void OnMouseUp(MouseButton changedButton, System.Windows.Point point, System.Windows.Size size) + { + RemoteDesktopProvider.MouseUp(changedButton, point, size); + } - }, (ex) => - { - Debug.WriteLine(ex); - }, () => { }); + public void OnMouseMove(System.Windows.Point point, System.Windows.Size size) + { + //RemoteDesktopProvider.MouseMove(point, size); } - private void StopRemoteDesktop() + public void OnMouseDoubleClick(MouseButton changedButton, System.Windows.Point point, System.Windows.Size size) { + RemoteDesktopProvider.MouseDoubleClick(changedButton, point, size); + } + public void OnKeyboardDown(Key key, bool ctrlDown, bool shitDown, bool altDown) + { + throw new NotImplementedException(); } + + public void OnKeyboardUp(Key key, bool ctrlDown, bool shitDown, bool altDown) + { + throw new NotImplementedException(); + } + + #endregion } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml index b6a4eec62..9d3308741 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml @@ -19,8 +19,8 @@ <ColumnDefinition Width="50*" /> <ColumnDefinition Width="50*" /> </Grid.ColumnDefinitions> - <Button Command="{Binding StopCommand}" Grid.Column="1" Style="{StaticResource FSE_RaisedButton_Dark_Hover}">STOP</Button> - <Button Command="{Binding StartCommand}" Grid.Column="2" Margin="10 0 0 0" Style="{StaticResource FSE_RaisedButton_Dark_Hover}">START</Button> + <Button Command="{Binding StopCommand}" IsEnabled="{Binding RemoteDesktopProvider.InSession}" Grid.Column="1" Style="{StaticResource FSE_RaisedButton_Dark_Hover}">STOP</Button> + <Button Command="{Binding StartCommand}" IsEnabled="{Binding RemoteDesktopProvider.CanStartSession}" Grid.Column="2" Margin="10 0 0 0" Style="{StaticResource FSE_RaisedButton_Dark_Hover}">START</Button> </Grid> <Border CornerRadius="15" BorderBrush="{StaticResource FSE_BorderBrush}" BorderThickness="1"> @@ -42,7 +42,7 @@ <Border.Background> <ImageBrush ImageSource="{StaticResource FSE_PPC_Back}" /> </Border.Background> - <Image Source="{Binding Source}" RenderOptions.BitmapScalingMode="Fant" Stretch="Fill"></Image> + <Image x:Name="img" Focusable="True" Source="{Binding Source}" RenderOptions.BitmapScalingMode="Fant" Stretch="Fill" Visibility="{Binding RemoteDesktopProvider.InSession,Converter={StaticResource BooleanToVisibilityConverter}}"></Image> </Border> </Border> </Border> diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml.cs index 27be82c5d..be7c280c8 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.PPCConsole/Views/RemoteDesktopView.xaml.cs @@ -12,6 +12,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; +using Tango.FSE.PPCConsole.ViewModels; namespace Tango.FSE.PPCConsole.Views { @@ -20,9 +21,50 @@ namespace Tango.FSE.PPCConsole.Views /// </summary> public partial class RemoteDesktopView : UserControl { + private RemoteDesktopViewVM _vm; + public RemoteDesktopView() { InitializeComponent(); + Loaded += (_, __) => _vm = DataContext as RemoteDesktopViewVM; + + img.PreviewMouseDown += Img_PreviewMouseDown; + img.PreviewMouseUp += Img_PreviewMouseUp; + img.PreviewMouseMove += Img_PreviewMouseMove; + img.PreviewKeyDown += Img_PreviewKeyDown; + img.PreviewKeyUp += Img_PreviewKeyUp; + } + + private void Img_PreviewKeyDown(object sender, KeyEventArgs e) + { + _vm.OnKeyboardDown(e.Key, Keyboard.IsKeyDown(Key.LeftCtrl), Keyboard.IsKeyDown(Key.LeftShift), Keyboard.IsKeyDown(Key.LeftAlt)); + } + + private void Img_PreviewKeyUp(object sender, KeyEventArgs e) + { + _vm.OnKeyboardUp(e.Key, Keyboard.IsKeyDown(Key.LeftCtrl), Keyboard.IsKeyDown(Key.LeftShift), Keyboard.IsKeyDown(Key.LeftAlt)); + } + + private void Img_PreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.ClickCount == 2) + { + _vm.OnMouseDoubleClick(e.ChangedButton, e.GetPosition(img), new Size(img.ActualWidth, img.ActualHeight)); + } + else + { + _vm.OnMouseDown(e.ChangedButton, e.GetPosition(img), new Size(img.ActualWidth, img.ActualHeight)); + } + } + + private void Img_PreviewMouseUp(object sender, MouseButtonEventArgs e) + { + _vm.OnMouseUp(e.ChangedButton, e.GetPosition(img), new Size(img.ActualWidth, img.ActualHeight)); + } + + private void Img_PreviewMouseMove(object sender, MouseEventArgs e) + { + _vm.OnMouseMove(e.GetPosition(img), new Size(img.ActualWidth, img.ActualHeight)); } } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs index c0abe6dab..bde374597 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/FSEViewModel.cs @@ -21,6 +21,7 @@ using Tango.FSE.Common.FSEApplication; using Tango.FSE.Common.Gateway; using Tango.FSE.Common.Navigation; using Tango.FSE.Common.Notifications; +using Tango.FSE.Common.RemoteDesktop; using Tango.Settings; using Tango.SharedUI; using static Tango.SharedUI.Controls.NavigationControl; @@ -84,6 +85,12 @@ namespace Tango.FSE.Common public IDiagnosticsProvider DiagnosticsProvider { get; set; } /// <summary> + /// Gets or sets the remote desktop provider. + /// </summary> + [TangoInject] + public IRemoteDesktopProvider RemoteDesktopProvider { get; set; } + + /// <summary> /// Gets or sets the FSE service. /// </summary> [TangoInject] diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/SnackbarItem.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/SnackbarItem.cs index 832b69d92..6246d0ce1 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/SnackbarItem.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Notifications/SnackbarItem.cs @@ -79,9 +79,9 @@ namespace Tango.FSE.Common.Notifications Task.Delay(TimeSpan.FromSeconds(2)).ContinueWith((y) => { Close(); - }, TaskScheduler.FromCurrentSynchronizationContext()); + }); } - }, TaskScheduler.FromCurrentSynchronizationContext()); + }); } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/RemoteDesktop/DesktopFrameReceivedEventArgs.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/RemoteDesktop/DesktopFrameReceivedEventArgs.cs new file mode 100644 index 000000000..1589c2b99 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/RemoteDesktop/DesktopFrameReceivedEventArgs.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.FSE.Common.RemoteDesktop +{ + public class DesktopFrameReceivedEventArgs : EventArgs + { + public BitmapSource Source { get; set; } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/RemoteDesktop/IRemoteDesktopProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.Common/RemoteDesktop/IRemoteDesktopProvider.cs new file mode 100644 index 000000000..716739d0a --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/RemoteDesktop/IRemoteDesktopProvider.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media.Imaging; + +namespace Tango.FSE.Common.RemoteDesktop +{ + public interface IRemoteDesktopProvider + { + event EventHandler<DesktopFrameReceivedEventArgs> FrameReceived; + bool InSession { get; } + bool CanStartSession { get; } + TimeSpan MouseMoveInterval { get; set; } + Task StartSession(); + Task EndSession(); + void MouseDown(MouseButton button, Point location, Size viewSize); + void MouseUp(MouseButton button, Point location, Size viewSize); + void MouseMove(Point location, Size viewSize); + void MouseDoubleClick(MouseButton button, Point location, Size viewSize); + void KeyboardDown(Key key, bool ctrlDown, bool shitDown, bool altDown); + void KeyboardUp(Key key, bool ctrlDown, bool shitDown, bool altDown); + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj index 664643f95..6e219c67a 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj +++ b/Software/Visual_Studio/FSE/Tango.FSE.Common/Tango.FSE.Common.csproj @@ -62,6 +62,8 @@ <Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.Data" /> <Reference Include="System.Runtime.Serialization" /> + <Reference Include="System.Windows" /> + <Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll</HintPath> </Reference> @@ -116,6 +118,8 @@ <Compile Include="Notifications\SnackbarItem.cs" /> <Compile Include="Notifications\TaskItem.cs" /> <Compile Include="FSEModuleBase.cs" /> + <Compile Include="RemoteDesktop\DesktopFrameReceivedEventArgs.cs" /> + <Compile Include="RemoteDesktop\IRemoteDesktopProvider.cs" /> <Compile Include="Threading\IDispatcherProvider.cs" /> <Compile Include="Web\FSEWebClient.cs" /> <Compile Include="Web\FSEWebClientBase.cs" /> @@ -183,7 +187,9 @@ <Generator>ResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.cs</LastGenOutput> </EmbeddedResource> - <None Include="App.config" /> + <None Include="App.config"> + <SubType>Designer</SubType> + </None> <Resource Include="Fonts\digital-7.ttf" /> <Resource Include="Fonts\Flexo-Black.otf" /> <Resource Include="Fonts\Flexo-BlackIt.otf" /> diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs index f76e8751d..40d2e58e7 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Notifications/DefaultNotificationProvider.cs @@ -542,8 +542,11 @@ namespace Tango.FSE.UI.Notifications /// <returns></returns> public TaskItem PushTaskItem(TaskItem taskBarItem) { - TaskItems.Add(taskBarItem); - RaisePropertyChanged(nameof(HasTaskItems)); + InvokeUI(() => + { + TaskItems.Add(taskBarItem); + RaisePropertyChanged(nameof(HasTaskItems)); + }); return taskBarItem; } @@ -573,8 +576,11 @@ namespace Tango.FSE.UI.Notifications /// <param name="taskBarItem"></param> public void PopTaskItem(TaskItem taskBarItem) { - TaskItems.Remove(taskBarItem); - RaisePropertyChanged(nameof(HasTaskItems)); + InvokeUI(() => + { + TaskItems.Remove(taskBarItem); + RaisePropertyChanged(nameof(HasTaskItems)); + }); } /// <summary> @@ -605,8 +611,11 @@ namespace Tango.FSE.UI.Notifications /// <returns></returns> public SnackbarItem PushSnackbarItem(SnackbarItem snackbarItem) { - SnackbarItems.Insert(0, snackbarItem); - RaisePropertyChanged(nameof(HasSnackbarItems)); + InvokeUI(() => + { + SnackbarItems.Insert(0, snackbarItem); + RaisePropertyChanged(nameof(HasSnackbarItems)); + }); return snackbarItem; } @@ -632,8 +641,11 @@ namespace Tango.FSE.UI.Notifications /// <param name="snackbarItem">The snack bar item.</param> public void PopSnackbarItem(SnackbarItem snackbarItem) { - SnackbarItems.Remove(snackbarItem); - RaisePropertyChanged(nameof(HasSnackbarItems)); + InvokeUI(() => + { + SnackbarItems.Remove(snackbarItem); + RaisePropertyChanged(nameof(HasSnackbarItems)); + }); } } } diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs new file mode 100644 index 000000000..3fc553127 --- /dev/null +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/RemoteDesktop/DefaultRemoteDesktopProvider.cs @@ -0,0 +1,341 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media.Imaging; +using Tango.Core; +using Tango.Core.DI; +using Tango.Core.Threading; +using Tango.FSE.Common.Connection; +using Tango.FSE.Common.Notifications; +using Tango.FSE.Common.RemoteDesktop; +using Tango.RemoteDesktop.Frames; +using Tango.RemoteDesktop.Network; +using Tango.Transport; + +namespace Tango.FSE.UI.RemoteDesktop +{ + public class DefaultRemoteDesktopProvider : ExtendedObject, IRemoteDesktopProvider + { + private class MouseMovement + { + public Point Location { get; set; } + public Size ViewSize { get; set; } + } + + private IMachineProvider _machineProvider; + private RasterFrame _currentFrame; + private Size? _frameSize; + private IntervalMessageDispatcher<StartRemoteDesktopSessionResponse> _frameDispatcher; + private Thread _mouseMoveThread; + private ProducerConsumerQueue<MouseMovement> _mouseMovements; + + public event EventHandler<DesktopFrameReceivedEventArgs> FrameReceived; + + [TangoInject] + private INotificationProvider NotificationProvider { get; set; } + + private bool _inSession; + public bool InSession + { + get { return _inSession; } + set { _inSession = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(CanStartSession)); } + } + + public TimeSpan MouseMoveInterval { get; set; } + + public bool CanStartSession + { + get { return _machineProvider.IsConnected && !InSession; } + } + + public DefaultRemoteDesktopProvider() + { + MouseMoveInterval = TimeSpan.FromMilliseconds(100); + } + + public DefaultRemoteDesktopProvider(IMachineProvider machineProvider) : this() + { + _machineProvider = machineProvider; + _machineProvider.MachineConnected += (_, __) => + { + InSession = false; + }; + _machineProvider.MachineDisconnected += (_, __) => + { + InSession = false; + OnFrameReceived(null); + }; + } + + public Task StartSession() + { + TaskCompletionSource<bool> completionSource = new TaskCompletionSource<bool>(); + + if (!CanStartSession) + { + throw new InvalidOperationException("Unable to start a remote desktop session at the moment."); + } + + if (!InSession) + { + InSession = true; + + var taskItem = NotificationProvider.PushTaskItem("Starting remote desktop session..."); + + if (_frameDispatcher != null) + { + _frameDispatcher.Dispose(); + } + + _frameDispatcher = new IntervalMessageDispatcher<StartRemoteDesktopSessionResponse>(OnRemoteDesktopResponse); + _frameDispatcher.Start(); + + bool taskCompleted = false; + + _machineProvider.MachineOperator.SendGenericContinuousRequest<StartRemoteDesktopSessionRequest, StartRemoteDesktopSessionResponse>(new StartRemoteDesktopSessionRequest() { }, new TransportContinuousRequestConfig() + { + Timeout = TimeSpan.FromSeconds(10), + }).Subscribe((response) => + { + if (!taskCompleted) + { + taskCompleted = true; + taskItem.Dispose(); + completionSource.SetResult(true); + } + + _frameDispatcher.Push(response); + + }, (ex) => + { + if (!taskCompleted) + { + taskCompleted = true; + taskItem.Dispose(); + completionSource.SetException(ex); + } + else if (InSession) + { + OnFailed(ex); + } + + InSession = false; + + //TODO: handle exception. + }, () => + { + //This cannot happen! + InSession = false; + OnFrameReceived(null); + }); + + return completionSource.Task; + } + else + { + return Task.FromResult(true); + } + } + + public async Task EndSession() + { + if (InSession) + { + using (NotificationProvider.PushTaskItem("Stopping remote desktop session...")) + { + await _machineProvider.MachineOperator.SendGenericRequest<StopRemoteDesktopSessionRequest, StopRemoteDesktopSessionResponse>(new StopRemoteDesktopSessionRequest()); + InSession = false; + } + } + } + + private void OnFailed(Exception exception) + { + InSession = false; + OnFrameReceived(null); + + if (!(exception is TransporterDisconnectedException)) + { + NotificationProvider.PushSnackbarItem(MessageType.Error, "Remote desktop session terminated unexpectedly.", true, null, TimeSpan.FromSeconds(5)); + } + } + + private void OnRemoteDesktopResponse(StartRemoteDesktopSessionResponse response) + { + if (!InSession) return; //To stop frame delivery immediately. + + if (response.Packet == null) + { + _frameDispatcher.Interval = 1000 / response.FrameRate; + return; //This is the first message with empty bitmap and only the frame rate. + } + + try + { + if (!response.Packet.IsPartial) + { + using (MemoryStream ms = new MemoryStream(response.Packet.Bitmap)) + { + if (_currentFrame != null) + { + _currentFrame.Dispose(); + } + + _currentFrame = new RasterFrame(new System.Drawing.Bitmap(ms)); + _frameSize = new Size(_currentFrame.Width, _currentFrame.Height); + } + } + else + { + using (MemoryStream ms = new MemoryStream(response.Packet.Bitmap)) + { + var diffFrame = new RasterFrame(new System.Drawing.Bitmap(ms), response.Packet.PartialRegion.Left, response.Packet.PartialRegion.Top); + diffFrame.Apply(_currentFrame); + diffFrame.Dispose(); + } + } + + OnFrameReceived(_currentFrame.ToBitmapSource()); + } + catch (Exception ex) + { + Debug.WriteLine($"Error on remote desktop packet received: {ex.Message}"); + } + } + + protected virtual void OnFrameReceived(BitmapSource source) + { + FrameReceived?.Invoke(this, new DesktopFrameReceivedEventArgs() + { + Source = source, + }); + } + + public async void MouseDown(MouseButton button, Point location, Size viewSize) + { + if (!InSession || _frameSize == null) return; + + await _machineProvider.MachineOperator.SendGenericRequest<MouseStateRequest, MouseStateResponse>(new MouseStateRequest() + { + Button = button, + EventType = MouseEventType.Down, + Location = TranslateLocation(location, viewSize) + }); + } + + public async void MouseUp(MouseButton button, Point location, Size viewSize) + { + if (!InSession || _frameSize == null) return; + + await _machineProvider.MachineOperator.SendGenericRequest<MouseStateRequest, MouseStateResponse>(new MouseStateRequest() + { + Button = button, + EventType = MouseEventType.Up, + Location = TranslateLocation(location, viewSize) + }); + } + + public void MouseMove(Point location, Size viewSize) + { + if (!InSession || _frameSize == null) return; + + if (_mouseMoveThread == null) + { + _mouseMovements = new ProducerConsumerQueue<MouseMovement>(); + _mouseMoveThread = new Thread(async () => + { + while (InSession) + { + MouseMovement movement = _mouseMovements.BlockDequeue(); + + while (_mouseMovements.Count > 0) + { + movement = _mouseMovements.BlockDequeue(); + } + + try + { + await _machineProvider.MachineOperator.SendGenericRequest<MouseStateRequest, MouseStateResponse>(new MouseStateRequest() + { + Button = MouseButton.Left, + EventType = MouseEventType.Move, + Location = TranslateLocation(movement.Location, movement.ViewSize) + }); + } + catch (Exception ex) + { + Debug.WriteLine($"Error on mouse move\n{ex.ToString()}"); + } + + Thread.Sleep(MouseMoveInterval); + } + + _mouseMoveThread = null; + }); + + _mouseMoveThread.IsBackground = true; + _mouseMoveThread.Start(); + } + + _mouseMovements.BlockEnqueue(new MouseMovement() + { + Location = location, + ViewSize = viewSize, + }); + } + + public async void MouseDoubleClick(MouseButton button, Point location, Size viewSize) + { + if (!InSession || _frameSize == null) return; + + await _machineProvider.MachineOperator.SendGenericRequest<MouseStateRequest, MouseStateResponse>(new MouseStateRequest() + { + Button = button, + EventType = MouseEventType.DoubleClick, + Location = TranslateLocation(location, viewSize) + }); + } + + private Point TranslateLocation(Point location, Size viewSize) + { + return new Point( + (location.X / viewSize.Width) * _frameSize.Value.Width, + (location.Y / viewSize.Height) * _frameSize.Value.Height); + } + + public async void KeyboardDown(Key key, bool ctrlDown, bool shitDown, bool altDown) + { + if (!InSession || _frameSize == null) return; + + await _machineProvider.MachineOperator.SendGenericRequest<KeyboardStateRequest, KeyboardStateResponse>(new KeyboardStateRequest() + { + EventType = KeyboardEventType.Down, + Key = key, + IsCtrlDown = ctrlDown, + IsShiftDown = shitDown, + IsAltDown = altDown, + }); + } + + public async void KeyboardUp(Key key, bool ctrlDown, bool shitDown, bool altDown) + { + if (!InSession || _frameSize == null) return; + + await _machineProvider.MachineOperator.SendGenericRequest<KeyboardStateRequest, KeyboardStateResponse>(new KeyboardStateRequest() + { + EventType = KeyboardEventType.Up, + Key = key, + IsCtrlDown = ctrlDown, + IsShiftDown = shitDown, + IsAltDown = altDown, + }); + } + } +} diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj b/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj index 7b3e64ff3..04d0e12d6 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Tango.FSE.UI.csproj @@ -65,6 +65,24 @@ <Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.Configuration" /> <Reference Include="System.Data" /> + <Reference Include="System.Drawing" /> + <Reference Include="System.Reactive.Core, Version=3.0.3000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Reactive.Core.3.1.1\lib\net46\System.Reactive.Core.dll</HintPath> + </Reference> + <Reference Include="System.Reactive.Interfaces, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll</HintPath> + </Reference> + <Reference Include="System.Reactive.Linq, Version=3.0.3000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Reactive.Linq.3.1.1\lib\net46\System.Reactive.Linq.dll</HintPath> + </Reference> + <Reference Include="System.Reactive.PlatformServices, Version=3.0.3000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Reactive.PlatformServices.3.1.1\lib\net46\System.Reactive.PlatformServices.dll</HintPath> + </Reference> + <Reference Include="System.Reactive.Windows.Threading, Version=3.0.1000.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263, processorArchitecture=MSIL"> + <HintPath>..\..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll</HintPath> + </Reference> + <Reference Include="System.Windows" /> + <Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll</HintPath> </Reference> @@ -128,6 +146,7 @@ <DependentUpon>MachineConnectionPane.xaml</DependentUpon> </Compile> <Compile Include="Panes\MachineConnectionPaneVM.cs" /> + <Compile Include="RemoteDesktop\DefaultRemoteDesktopProvider.cs" /> <Compile Include="Threading\DefaultDispatcherProvider.cs" /> <Compile Include="ViewModelLocator.cs" /> <Compile Include="ViewModels\AccountViewVM.cs" /> @@ -260,7 +279,9 @@ </None> </ItemGroup> <ItemGroup> - <None Include="App.config" /> + <None Include="App.config"> + <SubType>Designer</SubType> + </None> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\SideChains\Tango.AutoComplete\Tango.AutoComplete.csproj"> @@ -295,6 +316,10 @@ <Project>{e4927038-348d-4295-aaf4-861c58cb3943}</Project> <Name>Tango.PMR</Name> </ProjectReference> + <ProjectReference Include="..\..\Tango.RemoteDesktop\Tango.RemoteDesktop.csproj"> + <Project>{A78068D4-2061-4376-8EDE-583D8D880DEC}</Project> + <Name>Tango.RemoteDesktop</Name> + </ProjectReference> <ProjectReference Include="..\..\Tango.Settings\Tango.Settings.csproj"> <Project>{d8f1ad85-526a-4f50-b6dc-d437af63d8d8}</Project> <Name>Tango.Settings</Name> diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs index fac3d7018..536a42ae6 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModelLocator.cs @@ -14,6 +14,7 @@ using Tango.FSE.Common.Gateway; using Tango.FSE.Common.Modules; using Tango.FSE.Common.Navigation; using Tango.FSE.Common.Notifications; +using Tango.FSE.Common.RemoteDesktop; using Tango.FSE.Common.Threading; using Tango.FSE.Common.Web; using Tango.FSE.UI.Authentication; @@ -24,6 +25,7 @@ using Tango.FSE.UI.Gateway; using Tango.FSE.UI.Modules; using Tango.FSE.UI.Navigation; using Tango.FSE.UI.Notifications; +using Tango.FSE.UI.RemoteDesktop; using Tango.FSE.UI.Threading; using Tango.FSE.UI.ViewModels; @@ -43,6 +45,7 @@ namespace Tango.FSE.UI TangoIOC.Default.Unregister<IMachineProvider>(); TangoIOC.Default.Unregister<IFSEApplicationManager>(); TangoIOC.Default.Unregister<IDiagnosticsProvider>(); + TangoIOC.Default.Unregister<IRemoteDesktopProvider>(); //TangoIOC.Default.Unregister<ExternalBridgeScanner>(); //TangoIOC.Default.Unregister<IDiagnosticsFrameProvider>(); //TangoIOC.Default.Unregister<IEventLogger>(); @@ -60,8 +63,8 @@ namespace Tango.FSE.UI TangoIOC.Default.Register<FSEServicesContainer, FSEServicesContainer>(); TangoIOC.Default.Register<IFSEModuleLoader, DefaultFSEModuleLoader>(); TangoIOC.Default.Register<INavigationManager, DefaultNavigationManager>(); - //TangoIOC.Default.Register<IMachineProvider, DefaultMachineProvider>(); TangoIOC.Default.Register<IFSEApplicationManager, DefaultFSEApplicationManager>(); + TangoIOC.Default.Register<IRemoteDesktopProvider, DefaultRemoteDesktopProvider>(); TangoIOC.Default.Register<MainWindowVM>(); TangoIOC.Default.Register<MainViewVM>(); diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml index 36c1cba6f..dca8e2740 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Views/MainView.xaml @@ -104,8 +104,8 @@ </material:PackIcon> <commonControls:IconButton Visibility="{Binding CanClose,Converter={StaticResource BooleanToVisibilityConverter}}" Margin="0 -5 -3 0" Icon="Close" DockPanel.Dock="Right" Padding="8" Command="{Binding CloseCommand}" HorizontalAlignment="Right" VerticalAlignment="Top" Width="{Binding RelativeSource={RelativeSource Self},Path=ActualHeight}"/> <StackPanel Margin="10 8 0 0"> - <TextBlock Text="{Binding Title}" FontSize="{StaticResource FSE_SmallFontSize}"></TextBlock> - <TextBlock Margin="0 2 0 0" Text="{Binding Message}" FontSize="{StaticResource FSE_SmallerFontSize}" Foreground="{StaticResource FSE_GrayBrush}"></TextBlock> + <TextBlock TextWrapping="Wrap" Text="{Binding Title}" FontSize="{StaticResource FSE_SmallFontSize}"></TextBlock> + <TextBlock TextWrapping="Wrap" Margin="0 2 0 0" Text="{Binding Message}" FontSize="{StaticResource FSE_SmallerFontSize}" Foreground="{StaticResource FSE_GrayBrush}"></TextBlock> </StackPanel> </DockPanel> </DockPanel> diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config b/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config index dd8c723e4..d17a56d7d 100644 --- a/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config +++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/packages.config @@ -7,4 +7,10 @@ <package id="MaterialDesignColors" version="1.2.2" targetFramework="net461" /> <package id="MaterialDesignThemes" version="3.0.1" targetFramework="net461" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" /> + <package id="System.Reactive" version="3.1.1" targetFramework="net461" /> + <package id="System.Reactive.Core" version="3.1.1" targetFramework="net461" /> + <package id="System.Reactive.Interfaces" version="3.1.1" targetFramework="net461" /> + <package id="System.Reactive.Linq" version="3.1.1" targetFramework="net461" /> + <package id="System.Reactive.PlatformServices" version="3.1.1" targetFramework="net461" /> + <package id="System.Reactive.Windows.Threading" version="3.1.1" targetFramework="net461" /> </packages>
\ No newline at end of file diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs index 67d0b85de..0d5bd8559 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/DefaultRemoteDesktopService.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; using Tango.Core; using Tango.Core.DI; using Tango.Integration.ExternalBridge; @@ -14,9 +15,11 @@ using Tango.RemoteDesktop.CaptureMethods; using Tango.RemoteDesktop.Encoders; using Tango.RemoteDesktop.Engines; using Tango.RemoteDesktop.Frames; +using Tango.RemoteDesktop.Input; using Tango.RemoteDesktop.Network; using Tango.Settings; using Tango.Transport; +using static Tango.RemoteDesktop.Input.MouseController; namespace Tango.PPC.Common.RemoteDesktop { @@ -45,6 +48,8 @@ namespace Tango.PPC.Common.RemoteDesktop _clients = new List<RemoteDesktopClient>(); _engine = new RasterScreenCaptureEngine(); + _engine.CaptureCursor = false; + _engine.FrameRate = Math.Min(Math.Max(_settings.RemoteDesktopFrameRate, 1), 20); _engine.FrameReceived += _engine_FrameReceived; } @@ -56,6 +61,16 @@ namespace Tango.PPC.Common.RemoteDesktop _engine.CaptureMethod.Dispose(); var mainWindow = System.Windows.Application.Current.MainWindow; + mainWindow.LocationChanged += (_, __) => + { + _engine.CaptureRegion = new CaptureRegion() + { + Left = (int)mainWindow.Left, + Top = (int)mainWindow.Top, + Width = (int)mainWindow.Width, + Height = (int)mainWindow.Height + }; + }; _engine.CaptureRegion = new CaptureRegion() { @@ -68,11 +83,6 @@ namespace Tango.PPC.Common.RemoteDesktop #endif _engine.Comparer.MaxDifferencesThrow = _engine.CaptureRegion.Width * _engine.CaptureRegion.Height / 2; - - if (_settings.EnableRemoteDesktop) - { - Start(); - } } private bool _isStarted; @@ -82,24 +92,6 @@ namespace Tango.PPC.Common.RemoteDesktop private set { _isStarted = value; RaisePropertyChangedAuto(); } } - public void Start() - { - if (!IsStarted) - { - _engine.Start(); - IsStarted = true; - } - } - - public void Stop() - { - if (IsStarted) - { - _engine.Stop(); - IsStarted = false; - } - } - [ExternalBridgeRequestHandlerMethod(typeof(StartRemoteDesktopSessionRequest))] public async void OnStartRemoteDesktopSessionRequestReceived(StartRemoteDesktopSessionRequest request, String token, ExternalBridgeReceiver receiver) { @@ -119,10 +111,85 @@ namespace Tango.PPC.Common.RemoteDesktop await receiver.SendGenericResponse(new StartRemoteDesktopSessionResponse() { FrameRate = _engine.FrameRate - }, token, new TransportResponseConfig() + }, token); + + if (_settings.EnableRemoteDesktop) { - Immediate = false, - }); + if (!_engine.IsStarted) + { + _engine.Start(); + } + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(StopRemoteDesktopSessionRequest))] + public async void OnStopRemoteDesktopSessionRequestReceived(StopRemoteDesktopSessionRequest request, String token, ExternalBridgeReceiver receiver) + { + var client = _clients.SingleOrDefault(x => x.Receiver == receiver); + + if (client != null) + { + _clients.Remove(client); + } + + if (_clients.Count == 0) + { + _engine.Stop(); + } + + await receiver.SendGenericResponse(new StopRemoteDesktopSessionResponse(), token); + + if (client != null) + { + await receiver.SendGenericResponse(new StartRemoteDesktopSessionResponse(), client.Token, new TransportResponseConfig() { Completed = true }); + } + } + + [ExternalBridgeRequestHandlerMethod(typeof(MouseStateRequest))] + public async void OnMouseStateRequestReceived(MouseStateRequest request, String token, ExternalBridgeReceiver receiver) + { + MouseController.SetCursorPosition((int)request.Location.X, (int)request.Location.Y); + + if (request.EventType == MouseEventType.Up || request.EventType == MouseEventType.Down) + { + MouseEventFlags flag = MouseEventFlags.LeftUp; + + switch (request.EventType) + { + case MouseEventType.Down: + flag = request.Button == MouseButton.Right ? MouseEventFlags.RightDown : MouseEventFlags.LeftDown; + break; + case MouseEventType.Up: + flag = request.Button == MouseButton.Right ? MouseEventFlags.RightUp : MouseEventFlags.LeftUp; + break; + } + + MouseController.MouseEvent(flag); + } + else if (request.EventType == MouseEventType.DoubleClick) + { + MouseController.MouseEvent(MouseEventFlags.LeftDown); + MouseController.MouseEvent(MouseEventFlags.LeftUp); + MouseController.MouseEvent(MouseEventFlags.LeftDown); + MouseController.MouseEvent(MouseEventFlags.LeftUp); + } + + await receiver.SendGenericResponse(new MouseStateResponse(), token); + } + + [ExternalBridgeRequestHandlerMethod(typeof(KeyboardStateRequest))] + public async void OnKeyboardStateRequestReceived(KeyboardStateRequest request, String token, ExternalBridgeReceiver receiver) + { + if (request.EventType == KeyboardEventType.Down) + { + KeyboardController.KeyDown(request.Key, request.IsCtrlDown, request.IsShiftDown, request.IsAltDown); + } + else + { + KeyboardController.KeyUp(request.Key, request.IsCtrlDown, request.IsShiftDown, request.IsAltDown); + } + + await receiver.SendGenericResponse(new KeyboardStateResponse(), token); } private async void _engine_FrameReceived(object sender, ScreenCaptureFrameReceivedEventArgs<RasterFrame> e) @@ -211,6 +278,11 @@ namespace Tango.PPC.Common.RemoteDesktop { _clients.Remove(client); } + + if (_clients.Count == 0) + { + _engine.Stop(); + } } } } diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs index bce1bf51e..3c23c4832 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/RemoteDesktop/IRemoteDesktopService.cs @@ -9,7 +9,5 @@ namespace Tango.PPC.Common.RemoteDesktop public interface IRemoteDesktopService { bool IsStarted { get; } - void Start(); - void Stop(); } } diff --git a/Software/Visual_Studio/Tango.Core/Tango.Core.csproj b/Software/Visual_Studio/Tango.Core/Tango.Core.csproj index 8014ac3f2..605ba53dc 100644 --- a/Software/Visual_Studio/Tango.Core/Tango.Core.csproj +++ b/Software/Visual_Studio/Tango.Core/Tango.Core.csproj @@ -98,7 +98,7 @@ <Compile Include="IO\KnownFolders.cs" /> <Compile Include="Json\ProtobufContractResolver.cs" /> <Compile Include="Threading\ActionTimer.cs" /> - <Compile Include="Threading\SequencerThread.cs" /> + <Compile Include="Threading\IntervalMessageDispatcher.cs" /> <Compile Include="Threading\TaskSequencer.cs" /> <Compile Include="Threading\ThreadFactory.cs" /> <Compile Include="Threading\TimeoutTask.cs" /> @@ -206,7 +206,7 @@ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <ProjectExtensions> <VisualStudio> - <UserProperties BuildVersion_StartDate="2000/1/1" BuildVersion_UseGlobalSettings="False" BuildVersion_BuildVersioningStyle="None.None.Increment.TimeStamp" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_AssemblyInfoFilename="Properties\AssemblyInfo.cs" /> + <UserProperties BuildVersion_AssemblyInfoFilename="Properties\AssemblyInfo.cs" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_BuildVersioningStyle="None.None.Increment.TimeStamp" BuildVersion_UseGlobalSettings="False" BuildVersion_StartDate="2000/1/1" /> </VisualStudio> </ProjectExtensions> <Import Project="..\packages\System.Data.SQLite.Core.1.0.108.0\build\net46\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.108.0\build\net46\System.Data.SQLite.Core.targets')" /> diff --git a/Software/Visual_Studio/Tango.Core/Threading/IntervalMessageDispatcher.cs b/Software/Visual_Studio/Tango.Core/Threading/IntervalMessageDispatcher.cs new file mode 100644 index 000000000..ed61bea62 --- /dev/null +++ b/Software/Visual_Studio/Tango.Core/Threading/IntervalMessageDispatcher.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Tango.Core.Threading +{ + /// <summary> + /// Represents a message queue dispatcher for delivering messages at a constant interval. + /// </summary> + /// <typeparam name="TMessage"></typeparam> + /// <seealso cref="System.IDisposable" /> + public class IntervalMessageDispatcher<TMessage> : IDisposable + { + private Thread _queueThread; + private ProducerConsumerQueue<TMessage> _queue; + private Action<TMessage> _onNext; + + /// <summary> + /// Gets or sets the interval. + /// </summary> + public int Interval { get; set; } + + /// <summary> + /// Gets a value indicating whether this instance has started. + /// </summary> + public bool IsStarted { get; private set; } + + /// <summary> + /// Gets the number of queued messages. + /// </summary> + public int Count + { + get { return _queue.Count; } + } + + /// <summary> + /// Initializes a new instance of the <see cref="IntervalMessageDispatcher{T}"/> class. + /// </summary> + /// <param name="onNext">The delivery callback.</param> + public IntervalMessageDispatcher(Action<TMessage> onNext) + { + _onNext = onNext; + _queue = new ProducerConsumerQueue<TMessage>(); + } + + /// <summary> + /// Starts the dispatching of messages. + /// </summary> + public void Start() + { + if (!IsStarted) + { + IsStarted = true; + _queueThread = new Thread(QueueThreadMethod); + _queueThread.Name = "Sequencer Thread"; + _queueThread.IsBackground = true; + _queueThread.Start(); + } + } + + /// <summary> + /// Pushes the specified message. + /// </summary> + /// <param name="message">The message.</param> + public void Push(TMessage message) + { + _queue.BlockEnqueue(message); + } + + private void QueueThreadMethod() + { + Stopwatch watch = new Stopwatch(); + watch.Start(); + + while (IsStarted) + { + watch.Restart(); + var item = _queue.BlockDequeue(); + + if (!IsStarted) break; + + try + { + _onNext?.Invoke(item); + } + catch { } + + Thread.Sleep(Math.Max(1, (int)(Interval - watch.ElapsedMilliseconds))); + } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + public void Dispose() + { + IsStarted = false; + } + + /// <summary> + /// Creates a new instance of <see cref="IntervalMessageDispatcher{T}"/> and starts it immediately. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="onNext">The on next.</param> + /// <param name="interval">The interval.</param> + /// <returns></returns> + public static IntervalMessageDispatcher<T> StartNew<T>(Action<T> onNext, int interval) + { + IntervalMessageDispatcher<T> dispatcher = new IntervalMessageDispatcher<T>(onNext); + dispatcher.Interval = interval; + dispatcher.Start(); + return dispatcher; + } + } +} diff --git a/Software/Visual_Studio/Tango.Core/Threading/SequencerThread.cs b/Software/Visual_Studio/Tango.Core/Threading/SequencerThread.cs deleted file mode 100644 index 603790466..000000000 --- a/Software/Visual_Studio/Tango.Core/Threading/SequencerThread.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace Tango.Core.Threading -{ - public class SequencerThread<T> : IDisposable - { - private Thread _queueThread; - private ProducerConsumerQueue<T> _queue; - private Action<T> _onNext; - - public int FrameRate { get; set; } - - public bool IsStarted { get; private set; } - - public SequencerThread(Action<T> onNext) - { - _onNext = onNext; - _queue = new ProducerConsumerQueue<T>(); - } - - public void Start() - { - if (!IsStarted) - { - IsStarted = true; - _queueThread = new Thread(QueueThreadMethod); - _queueThread.Name = "Sequencer Thread"; - _queueThread.IsBackground = true; - _queueThread.Start(); - } - } - - public void Push(T item) - { - _queue.BlockEnqueue(item); - } - - private void QueueThreadMethod() - { - Stopwatch watch = new Stopwatch(); - watch.Start(); - - while (IsStarted) - { - watch.Restart(); - var item = _queue.BlockDequeue(); - - if (!IsStarted) break; - - try - { - _onNext?.Invoke(item); - } - catch { } - - Thread.Sleep(Math.Max(1, (int)(FrameRate - watch.ElapsedMilliseconds))); - } - } - - public void Dispose() - { - IsStarted = false; - } - } -} diff --git a/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeTcpClient.cs b/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeTcpClient.cs index 28ebc5341..5b59429d7 100644 --- a/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeTcpClient.cs +++ b/Software/Visual_Studio/Tango.Integration/ExternalBridge/ExternalBridgeTcpClient.cs @@ -223,14 +223,16 @@ namespace Tango.Integration.ExternalBridge try { var response = await SendRequest<ExternalBridgeLogoutRequest, ExternalBridgeLogoutResponse>(request, new TransportRequestConfig() { ShouldLog = true }); - - Status = MachineStatuses.Standby; } catch { } + + Status = MachineStatuses.Standby; } State = TransportComponentState.Disconnected; + NotifyContinuousRequestMessagesDisconnection(); + SessionLogger.EndSession(); if (Adapter != null) diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Input/KeyboardController.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Input/KeyboardController.cs new file mode 100644 index 000000000..832018dac --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Input/KeyboardController.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using WindowsInput; +using WindowsInput.Native; + +namespace Tango.RemoteDesktop.Input +{ + public static class KeyboardController + { + private static InputSimulator simulator; + + static KeyboardController() + { + simulator = new InputSimulator(); + } + + public static void KeyDown(Key key, bool ctrlDown, bool shitDown, bool altDown) + { + VirtualKeyCode virtualKey = (VirtualKeyCode)KeyInterop.VirtualKeyFromKey(key); + + if (ctrlDown || shitDown || altDown) + { + List<VirtualKeyCode> modifierKeys = new List<VirtualKeyCode>(); + + if (ctrlDown) + { + modifierKeys.Add(VirtualKeyCode.LCONTROL); + } + if (shitDown) + { + modifierKeys.Add(VirtualKeyCode.LSHIFT); + } + if (altDown) + { + modifierKeys.Add(VirtualKeyCode.MENU); + } + + simulator.Keyboard.ModifiedKeyStroke(modifierKeys, virtualKey); + } + else + { + simulator.Keyboard.KeyDown(virtualKey); + } + } + + public static void KeyUp(Key key, bool ctrlDown, bool shitDown, bool altDown) + { + VirtualKeyCode virtualKey = (VirtualKeyCode)KeyInterop.VirtualKeyFromKey(key); + + if (ctrlDown || shitDown || altDown) + { + List<VirtualKeyCode> modifierKeys = new List<VirtualKeyCode>(); + + if (ctrlDown) + { + modifierKeys.Add(VirtualKeyCode.LCONTROL); + } + if (shitDown) + { + modifierKeys.Add(VirtualKeyCode.LSHIFT); + } + if (altDown) + { + modifierKeys.Add(VirtualKeyCode.MENU); + } + + simulator.Keyboard.ModifiedKeyStroke(modifierKeys, virtualKey); + } + else + { + simulator.Keyboard.KeyUp(virtualKey); + } + } + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Input/MouseController.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Input/MouseController.cs new file mode 100644 index 000000000..ec556f88c --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Input/MouseController.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Input +{ + public static class MouseController + { + [Flags] + public enum MouseEventFlags + { + LeftDown = 0x00000002, + LeftUp = 0x00000004, + MiddleDown = 0x00000020, + MiddleUp = 0x00000040, + Move = 0x00000001, + Absolute = 0x00008000, + RightDown = 0x00000008, + RightUp = 0x00000010 + } + + [DllImport("user32.dll", EntryPoint = "SetCursorPos")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetCursorPos(int x, int y); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetCursorPos(out MousePoint lpMousePoint); + + [DllImport("user32.dll")] + private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); + + public static void SetCursorPosition(int x, int y) + { + SetCursorPos(x, y); + } + + public static void SetCursorPosition(MousePoint point) + { + SetCursorPos(point.X, point.Y); + } + + public static MousePoint GetCursorPosition() + { + MousePoint currentMousePoint; + var gotPoint = GetCursorPos(out currentMousePoint); + if (!gotPoint) { currentMousePoint = new MousePoint(0, 0); } + return currentMousePoint; + } + + public static void MouseEvent(MouseEventFlags value) + { + MousePoint position = GetCursorPosition(); + + mouse_event + ((int)value, + position.X, + position.Y, + 0, + 0) + ; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MousePoint + { + public int X; + public int Y; + + public MousePoint(int x, int y) + { + X = x; + Y = y; + } + } + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardEventType.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardEventType.cs new file mode 100644 index 000000000..b1b5b0353 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardEventType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Network +{ + public enum KeyboardEventType + { + Up, + Down + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardStateRequest.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardStateRequest.cs new file mode 100644 index 000000000..27ae49f64 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardStateRequest.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Tango.RemoteDesktop.Network +{ + public class KeyboardStateRequest + { + public KeyboardEventType EventType { get; set; } + public Key Key { get; set; } + public bool IsCtrlDown { get; set; } + public bool IsShiftDown { get; set; } + public bool IsAltDown { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardStateResponse.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardStateResponse.cs new file mode 100644 index 000000000..6544078f0 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/KeyboardStateResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Network +{ + public class KeyboardStateResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseEventType.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseEventType.cs new file mode 100644 index 000000000..2c9f84e03 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseEventType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Network +{ + public enum MouseEventType + { + Down, + Up, + Move, + DoubleClick + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseStateRequest.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseStateRequest.cs new file mode 100644 index 000000000..36e98351f --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseStateRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; + +namespace Tango.RemoteDesktop.Network +{ + public class MouseStateRequest + { + public MouseButton Button { get; set; } + public MouseEventType EventType { get; set; } + public Point Location { get; set; } + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseStateResponse.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseStateResponse.cs new file mode 100644 index 000000000..f67715d99 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/MouseStateResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Network +{ + public class MouseStateResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/StopRemoteDesktopSessionRequest.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/StopRemoteDesktopSessionRequest.cs new file mode 100644 index 000000000..058f5bbe1 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/StopRemoteDesktopSessionRequest.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Network +{ + public class StopRemoteDesktopSessionRequest + { + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Network/StopRemoteDesktopSessionResponse.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Network/StopRemoteDesktopSessionResponse.cs new file mode 100644 index 000000000..92a239d11 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Network/StopRemoteDesktopSessionResponse.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Network +{ + public class StopRemoteDesktopSessionResponse + { + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs index 81efb6a30..8920b983b 100644 --- a/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs +++ b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs @@ -111,7 +111,6 @@ namespace Tango.RemoteDesktop if (IsStarted) { IsStarted = false; - _previousBitmap?.Dispose(); } } @@ -180,15 +179,20 @@ namespace Tango.RemoteDesktop catch (Exception ex) { Debug.WriteLine($"Error in screen capture engine: {ex.Message}"); + _previousBitmap = null; } int delay = Math.Max(5, (1000 / FrameRate) - (int)watch.ElapsedMilliseconds); Thread.Sleep(delay); } + + _previousBitmap = null; } private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame, uint differenceCount) { + if (!IsStarted) return; + FrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs<TFrame>() { Frame = new ScreenCaptureFrame<TFrame>(currentBitmap, diffFrame) diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj b/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj index b0feb438d..dfffe90ec 100644 --- a/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj @@ -66,6 +66,9 @@ <Reference Include="System.Net.Http" /> <Reference Include="System.Xml" /> <Reference Include="WindowsBase" /> + <Reference Include="WindowsInput, Version=1.0.4.0, Culture=neutral, PublicKeyToken=9b287f7dc5073cad, processorArchitecture=MSIL"> + <HintPath>..\packages\InputSimulator.1.0.4.0\lib\net20\WindowsInput.dll</HintPath> + </Reference> </ItemGroup> <ItemGroup> <Compile Include="BitmapComparerResult.cs" /> @@ -82,11 +85,21 @@ <Compile Include="Engines\RasterScreenCaptureEngine.cs" /> <Compile Include="Engines\VectorScreenCaptureEngine.cs" /> <Compile Include="Frames\VectorFrameColor.cs" /> + <Compile Include="Input\KeyboardController.cs" /> + <Compile Include="Input\MouseController.cs" /> <Compile Include="IScreenCaptureEngine.cs" /> <Compile Include="MaxDifferencesReachedException.cs" /> + <Compile Include="Network\KeyboardEventType.cs" /> + <Compile Include="Network\KeyboardStateRequest.cs" /> + <Compile Include="Network\KeyboardStateResponse.cs" /> + <Compile Include="Network\MouseEventType.cs" /> + <Compile Include="Network\MouseStateRequest.cs" /> + <Compile Include="Network\MouseStateResponse.cs" /> <Compile Include="Network\RemoteDesktopPacket.cs" /> <Compile Include="Network\StartRemoteDesktopSessionRequest.cs" /> <Compile Include="Network\StartRemoteDesktopSessionResponse.cs" /> + <Compile Include="Network\StopRemoteDesktopSessionRequest.cs" /> + <Compile Include="Network\StopRemoteDesktopSessionResponse.cs" /> <Compile Include="Quantization\ColorBgra.cs" /> <Compile Include="Quantization\OctreeQuantizer.cs" /> <Compile Include="Quantization\PaletteTable.cs" /> diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/packages.config b/Software/Visual_Studio/Tango.RemoteDesktop/packages.config index 2cbb3038a..67634c01e 100644 --- a/Software/Visual_Studio/Tango.RemoteDesktop/packages.config +++ b/Software/Visual_Studio/Tango.RemoteDesktop/packages.config @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="InputSimulator" version="1.0.4.0" targetFramework="net461" /> <package id="Quamotion.TurboJpegWrapper" version="1.5.69" targetFramework="net461" /> </packages>
\ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Transport/TransporterBase.cs b/Software/Visual_Studio/Tango.Transport/TransporterBase.cs index f61e29795..45b89f9ff 100644 --- a/Software/Visual_Studio/Tango.Transport/TransporterBase.cs +++ b/Software/Visual_Studio/Tango.Transport/TransporterBase.cs @@ -21,6 +21,8 @@ using System.IO; using Tango.Core.ExtensionMethods; using Tango.PMR.Integration; using Newtonsoft.Json; +using System.Diagnostics; +using System.Reactive.Concurrency; namespace Tango.Transport { @@ -333,6 +335,7 @@ namespace Tango.Transport { try { + _pendingRequests.Remove(request); LogManager.Log($"Notifying continuous request '{(request.Message as ITangoMessage).Type}'..."); OnRequestFailed(request, new TransporterDisconnectedException("Transporter disconnected.")); request.SetException(new TransporterDisconnectedException("Transporter disconnected.")); @@ -908,8 +911,10 @@ namespace Tango.Transport var responseObject = JsonConvert.DeserializeObject<Response>(response.Message.Data.ToStringUtf8()); subject.OnNext(responseObject); } - catch + catch (Exception ex) { + Debugger.Break(); + System.Diagnostics.Debug.WriteLine($"Exception thrown by the generic request continuous handler.\n{ex.ToString()}"); //Ignore exception at the client side. } @@ -920,8 +925,10 @@ namespace Tango.Transport { subject.OnError(ex); } - catch + catch (Exception xx) { + Debugger.Break(); + System.Diagnostics.Debug.WriteLine($"Exception thrown by the generic request error handler.\n{xx.ToString()}"); //Ignore exception at the client side. } @@ -931,8 +938,10 @@ namespace Tango.Transport { subject.OnCompleted(); } - catch + catch (Exception ex) { + Debugger.Break(); + System.Diagnostics.Debug.WriteLine($"Exception thrown by the generic request completed handler.\n{ex.ToString()}"); //Ignore exception at the client side. } }); @@ -1052,7 +1061,7 @@ namespace Tango.Transport { config = config ?? new TransportResponseConfig(); - if (_pushThread == null || _pushThread.ThreadState == ThreadState.Aborted) + if (_pushThread == null || _pushThread.ThreadState == System.Threading.ThreadState.Aborted) { throw new InvalidOperationException("Transporter push thread is not in a running state."); } |
