diff --git a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs index 16ac024..8515bc3 100644 --- a/HyperionScreenCap/Capture/Dx11ScreenCapture.cs +++ b/HyperionScreenCap/Capture/Dx11ScreenCapture.cs @@ -3,18 +3,23 @@ using SharpDX.Direct3D11; using SharpDX.DXGI; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; +using log4net; namespace HyperionScreenCap { + // Custom exception to indicate that capture is paused + public class CapturePausedException : Exception + { + public CapturePausedException(string message) + : base(message) + { + } + } + class DX11ScreenCapture : IScreenCapture { private int _adapterIndex; @@ -38,23 +43,24 @@ class DX11ScreenCapture : IScreenCapture private byte[] _lastCapturedFrame; private int _minCaptureTime; private Stopwatch _captureTimer; - private bool _desktopDuplicatorInvalid; private bool _disposed; public int CaptureWidth { get; private set; } public int CaptureHeight { get; private set; } + private static readonly ILog LOG = LogManager.GetLogger(typeof(DX11ScreenCapture)); + public static String GetAvailableMonitors() { StringBuilder response = new StringBuilder(); - using ( Factory1 factory = new Factory1() ) + using (Factory1 factory = new Factory1()) { int adapterIndex = 0; - foreach(Adapter adapter in factory.Adapters) + foreach (Adapter adapter in factory.Adapters) { response.Append($"Adapter Index {adapterIndex++}: {adapter.Description.Description}\n"); int outputIndex = 0; - foreach(Output output in adapter.Outputs) + foreach (Output output in adapter.Outputs) { response.Append($"\tMonitor Index {outputIndex++}: {output.Description.DeviceName}"); var desktopBounds = output.Description.DesktopBounds; @@ -78,18 +84,18 @@ public DX11ScreenCapture(int adapterIndex, int monitorIndex, int scalingFactor, public void Initialize() { + Dispose(); // Ensure previous resources are released + int mipLevels; - if ( _scalingFactor == 1 ) + if (_scalingFactor == 1) mipLevels = 1; - else if ( _scalingFactor > 0 && _scalingFactor % 2 == 0 ) + else if (_scalingFactor > 0 && _scalingFactor % 2 == 0) { - /// Mip level for a scaling factor other than one is computed as follows: - /// 2^n = 2 + n - 1 where LHS is the scaling factor and RHS is the MipLevels value. _scalingFactorLog2 = Convert.ToInt32(Math.Log(_scalingFactor, 2)); mipLevels = 2 + _scalingFactorLog2 - 1; } else - throw new Exception("Invalid scaling factor. Allowed valued are 1, 2, 4, etc."); + throw new Exception("Invalid scaling factor. Allowed values are 1, 2, 4, etc."); // Create DXGI Factory1 _factory = new Factory1(); @@ -100,7 +106,16 @@ public void Initialize() // Get DXGI.Output _output = _adapter.GetOutput(_monitorIndex); + if (_output == null) + { + throw new Exception($"Failed to get output for monitor index {_monitorIndex}."); + } + _output1 = _output.QueryInterface(); + if (_output1 == null) + { + throw new Exception("Failed to get Output1 interface."); + } // Width/Height of desktop to capture var desktopBounds = _output.Description.DesktopBounds; @@ -152,71 +167,100 @@ public void Initialize() private void InitDesktopDuplicator() { - // Duplicate the output - _duplicatedOutput = _output1.DuplicateOutput(_device); + try + { + _duplicatedOutput?.Dispose(); + _duplicatedOutput = null; - _desktopDuplicatorInvalid = false; + if (_output1 == null || _device == null) + { + throw new Exception("Output1 or Device is null. Cannot initialize desktop duplicator."); + } + + _duplicatedOutput = _output1.DuplicateOutput(_device); + LOG.Debug("Desktop duplicator initialized successfully."); + } + catch (SharpDXException ex) + { + LOG.Error($"SharpDXException in InitDesktopDuplicator: {ex.Message}", ex); + throw; + } } public byte[] Capture() { - if ( _desktopDuplicatorInvalid ) + _captureTimer.Restart(); + try { - _duplicatedOutput?.Dispose(); - InitDesktopDuplicator(); + byte[] response = ManagedCapture(); + _captureTimer.Stop(); + return response; + } + catch (SharpDXException ex) + { + if (ex.ResultCode == SharpDX.DXGI.ResultCode.AccessLost || + ex.ResultCode == SharpDX.DXGI.ResultCode.AccessDenied || + ex.ResultCode == SharpDX.DXGI.ResultCode.SessionDisconnected || + ex.ResultCode == SharpDX.DXGI.ResultCode.DeviceRemoved || + ex.ResultCode == SharpDX.DXGI.ResultCode.InvalidCall) + { + LOG.Warn($"Capture failed due to device loss: {ex.Message}. Disposing and signaling for reinitialization."); + Dispose(); + throw new CapturePausedException("Capture is paused due to device loss."); + } + else + { + LOG.Error($"Capture failed: {ex.Message}", ex); + throw; + } + } + catch (Exception ex) + { + LOG.Error($"Capture failed: {ex.Message}", ex); + throw; } - - _captureTimer.Restart(); - byte[] response = ManagedCapture(); - _captureTimer.Stop(); - - return response; } private byte[] ManagedCapture() { + if (_duplicatedOutput == null) + { + throw new Exception("DuplicatedOutput is null. Cannot capture."); + } + SharpDX.DXGI.Resource screenResource = null; OutputDuplicateFrameInformation duplicateFrameInformation; try { - try - { - // Try to get duplicated frame within given time - _duplicatedOutput.AcquireNextFrame(_frameCaptureTimeout, out duplicateFrameInformation, out screenResource); + // Try to get duplicated frame within given time + _duplicatedOutput.AcquireNextFrame(_frameCaptureTimeout, out duplicateFrameInformation, out screenResource); - if ( duplicateFrameInformation.LastPresentTime == 0 && _lastCapturedFrame != null ) - return _lastCapturedFrame; - } - catch ( SharpDXException ex ) - { - if ( ex.ResultCode.Code == SharpDX.DXGI.ResultCode.WaitTimeout.Code && _lastCapturedFrame != null ) - return _lastCapturedFrame; - - if ( ex.ResultCode.Code == SharpDX.DXGI.ResultCode.AccessLost.Code ) - _desktopDuplicatorInvalid = true; - - throw ex; - } + if (duplicateFrameInformation.LastPresentTime == 0 && _lastCapturedFrame != null) + return _lastCapturedFrame; // Check if scaling is used - if ( CaptureWidth != _width ) + if (CaptureWidth != _width) { // Copy resource into memory that can be accessed by the CPU - using ( var screenTexture2D = screenResource.QueryInterface() ) + using (var screenTexture2D = screenResource.QueryInterface()) + { _device.ImmediateContext.CopySubresourceRegion(screenTexture2D, 0, null, _smallerTexture, 0); + } // Generates the mipmap of the screen _device.ImmediateContext.GenerateMips(_smallerTextureView); - // Copy the mipmap of smallerTexture (size/ scalingFactor) to the staging texture: 1 for /2, 2 for /4...etc + // Copy the mipmap of smallerTexture (size/ scalingFactor) to the staging texture _device.ImmediateContext.CopySubresourceRegion(_smallerTexture, _scalingFactorLog2, null, _stagingTexture, 0); } else { // Copy resource into memory that can be accessed by the CPU - using ( var screenTexture2D = screenResource.QueryInterface() ) + using (var screenTexture2D = screenResource.QueryInterface()) + { _device.ImmediateContext.CopyResource(screenTexture2D, _stagingTexture); + } } // Get the desktop capture texture @@ -228,9 +272,9 @@ private byte[] ManagedCapture() { screenResource?.Dispose(); // Fixed OUT_OF_MEMORY issue on AMD Radeon cards. Ignoring all exceptions during unmapping. - try { _device.ImmediateContext.UnmapSubresource(_stagingTexture, 0); } catch { }; + try { _device?.ImmediateContext?.UnmapSubresource(_stagingTexture, 0); } catch { }; // Ignore DXGI_ERROR_INVALID_CALL, DXGI_ERROR_ACCESS_LOST errors since capture is already complete - try { _duplicatedOutput.ReleaseFrame(); } catch { } + try { _duplicatedOutput?.ReleaseFrame(); } catch { } } } @@ -245,15 +289,15 @@ private byte[] ToRGBArray(DataBox mapSource) var sourcePtr = mapSource.DataPointer; byte[] bytes = new byte[CaptureWidth * 3 * CaptureHeight]; int byteIndex = 0; - for ( int y = 0; y < CaptureHeight; y++ ) + for (int y = 0; y < CaptureHeight; y++) { Int32[] rowData = new Int32[CaptureWidth]; Marshal.Copy(sourcePtr, rowData, 0, CaptureWidth); - foreach ( Int32 pixelData in rowData ) + foreach (Int32 pixelData in rowData) { byte[] values = BitConverter.GetBytes(pixelData); - if ( BitConverter.IsLittleEndian ) + if (BitConverter.IsLittleEndian) { // Byte order : bgra bytes[byteIndex++] = values[2]; @@ -277,7 +321,7 @@ private byte[] ToRGBArray(DataBox mapSource) public void DelayNextCapture() { int remainingFrameTime = _minCaptureTime - (int)_captureTimer.ElapsedMilliseconds; - if ( remainingFrameTime > 0 ) + if (remainingFrameTime > 0) { Thread.Sleep(remainingFrameTime); } @@ -285,15 +329,33 @@ public void DelayNextCapture() public void Dispose() { - _duplicatedOutput?.Dispose(); - _output1?.Dispose(); - _output?.Dispose(); - _stagingTexture?.Dispose(); - _smallerTexture?.Dispose(); - _smallerTextureView?.Dispose(); - _device?.Dispose(); - _adapter?.Dispose(); - _factory?.Dispose(); + try { _duplicatedOutput?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _duplicatedOutput", ex); } + _duplicatedOutput = null; + + try { _output1?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _output1", ex); } + _output1 = null; + + try { _output?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _output", ex); } + _output = null; + + try { _stagingTexture?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _stagingTexture", ex); } + _stagingTexture = null; + + try { _smallerTexture?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _smallerTexture", ex); } + _smallerTexture = null; + + try { _smallerTextureView?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _smallerTextureView", ex); } + _smallerTextureView = null; + + try { _device?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _device", ex); } + _device = null; + + try { _adapter?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _adapter", ex); } + _adapter = null; + + try { _factory?.Dispose(); } catch (Exception ex) { LOG.Error("Exception during Dispose of _factory", ex); } + _factory = null; + _lastCapturedFrame = null; _disposed = true; } diff --git a/HyperionScreenCap/Config/AppConstants.cs b/HyperionScreenCap/Config/AppConstants.cs index 1285451..35f45c3 100644 --- a/HyperionScreenCap/Config/AppConstants.cs +++ b/HyperionScreenCap/Config/AppConstants.cs @@ -10,6 +10,11 @@ class AppConstants /// public const int MAX_CAPTURE_ATTEMPTS = 45; + /// + /// Delay in milliseconds before retrying after a pause + /// + public const int CAPTURE_PAUSED_RETRY_DELAY = 3000; + /// /// Number of screen capture failure attemps after which screen capture should be re-initialized. /// diff --git a/HyperionScreenCap/Form/MainForm.cs b/HyperionScreenCap/Form/MainForm.cs index 2cb9121..e8a3161 100644 --- a/HyperionScreenCap/Form/MainForm.cs +++ b/HyperionScreenCap/Form/MainForm.cs @@ -254,14 +254,16 @@ private void EnableCapture() } LOG.Info($"Enabling {SettingsManager.HyperionTaskConfigurations.Count} screen capture(s)"); - foreach ( HyperionTaskConfiguration configuration in SettingsManager.HyperionTaskConfigurations) + foreach (HyperionTaskConfiguration configuration in SettingsManager.HyperionTaskConfigurations) { if (configuration.Enabled) { HyperionTask hyperionTask = new HyperionTask(configuration, _notificationUtils); + hyperionTask.OnCaptureDisabled += HyperionTask_OnCaptureDisabled; hyperionTask.EnableCapture(); _hyperionTasks.Add(hyperionTask); - } else + } + else { LOG.Info($"Capture task with ID {configuration.Id} is disabled. Skipping."); } @@ -271,6 +273,20 @@ private void EnableCapture() LOG.Info($"Enabled {_hyperionTasks.Count} screen capture(s)"); } + private void HyperionTask_OnCaptureDisabled(object sender, EventArgs e) + { + if (InvokeRequired) + { + Invoke(new Action(() => HyperionTask_OnCaptureDisabled(sender, e))); + return; + } + + LOG.Warn("Capture disabled event received. Attempting to restart capture."); + ToggleCapture(CaptureCommand.OFF); + Thread.Sleep(2000); // Wait a bit before restarting + ToggleCapture(CaptureCommand.ON); + } + private void DisableCapture() { LOG.Info($"Disabling {_hyperionTasks.Count} screen capture(s)"); @@ -285,19 +301,40 @@ private void DisableCapture() private void DisableCaptureOnFailure() { - while ( CaptureEnabled ) + int consecutiveFailures = 0; + while (CaptureEnabled) { - foreach ( HyperionTask task in _hyperionTasks ) + bool allTasksFailed = true; + foreach (HyperionTask task in _hyperionTasks) { - if ( !task.CaptureEnabled ) + if (task.CaptureEnabled) { - // We have found a task for which capture has been disabled due to failure - // Turning off capture and exiting this thread - LOG.Error($"Found {task} with capture disabled due to failure. Issuing OFF command."); + allTasksFailed = false; + } + else + { + LOG.Error($"Found {task} with capture disabled due to failure. Attempting to restart."); + task.RestartCapture(); + } + } + + if (allTasksFailed) + { + consecutiveFailures++; + if (consecutiveFailures > 5) // Arbitrary number, adjust as needed + { + LOG.Error("All tasks failed consecutively. Performing full restart."); ToggleCapture(CaptureCommand.OFF, false, false); - return; + Thread.Sleep(2000); // Wait a bit before restarting + ToggleCapture(CaptureCommand.ON, false, false); + consecutiveFailures = 0; } } + else + { + consecutiveFailures = 0; + } + Thread.Sleep(AppConstants.CAPTURE_FAILURE_DETECTION_INTERVAL); } } diff --git a/HyperionScreenCap/Helper/HyperionTask.cs b/HyperionScreenCap/Helper/HyperionTask.cs index d7780fd..101be03 100644 --- a/HyperionScreenCap/Helper/HyperionTask.cs +++ b/HyperionScreenCap/Helper/HyperionTask.cs @@ -21,6 +21,8 @@ class HyperionTask // TODO: Remove notifications from here public bool CaptureEnabled { get; private set; } private Thread _captureThread; + public event EventHandler OnCaptureDisabled; + public HyperionTask(HyperionTaskConfiguration configuration, NotificationUtils notificationUtils) { this._configuration = configuration; @@ -30,7 +32,7 @@ public HyperionTask(HyperionTaskConfiguration configuration, NotificationUtils n private void InitScreenCapture() { - if ( _screenCapture != null && !_screenCapture.IsDisposed() ) + if (_screenCapture != null && !_screenCapture.IsDisposed()) { // Screen capture already initialized. Ignoring request. return; @@ -41,7 +43,7 @@ private void InitScreenCapture() _screenCapture.Initialize(); LOG.Info($"{this}: Screen capture initialized"); } - catch ( Exception ex ) + catch (Exception ex) { _screenCapture?.Dispose(); throw new Exception("Failed to initialize screen capture: " + ex.Message, ex); @@ -103,93 +105,160 @@ private void DisposeHyperionClients() private void ConnectHyperionClients() { - foreach ( HyperionClient hyperionClient in _hyperionClients ) + foreach (HyperionClient hyperionClient in _hyperionClients) { - if ( hyperionClient.IsConnected() ) - { - // Hyperion client already initialized. Ignoring request. - return; - } try { LOG.Info($"{this}: Connecting {hyperionClient}"); - hyperionClient?.Dispose(); - // TODO: check for memory leak in each of the Hyperion Clients - hyperionClient.Connect(); - // Double checking since sometimes exceptions are not thrown even if connection fails - if ( hyperionClient.IsConnected() ) + hyperionClient.Dispose(); // Ensure any existing connection is closed + hyperionClient.Connect(); // This will now send registration + if (hyperionClient.IsConnected()) { LOG.Info($"{this}: {hyperionClient} connected"); _notificationUtils.Info($"Connected to Hyperion server using {hyperionClient}!"); + + // Only send initial frame if screen capture is initialized + if (_screenCapture != null) + { + hyperionClient.SendInitialFrame(_screenCapture.CaptureWidth, _screenCapture.CaptureHeight); + + // Send an actual captured frame immediately + byte[] initialFrame = CaptureInitialFrame(); + hyperionClient.SendImageData(initialFrame, _screenCapture.CaptureWidth, _screenCapture.CaptureHeight); + } } else + { throw new Exception(GetHyperionInitFailedMsg(hyperionClient)); + } } - catch ( Exception ex ) + catch (Exception ex) { - throw new Exception(GetHyperionInitFailedMsg(hyperionClient), ex); + LOG.Error($"{this}: Failed to connect to Hyperion server: {ex.Message}", ex); + throw; } } } + private byte[] CaptureInitialFrame() + { + try + { + return _screenCapture.Capture(); + } + catch (Exception ex) + { + LOG.Error($"{this}: Failed to capture initial frame: {ex.Message}", ex); + // Return a black frame as a fallback + return new byte[_screenCapture.CaptureWidth * _screenCapture.CaptureHeight * 3]; + } + } + private void TransmitNextFrame() { - foreach ( HyperionClient hyperionClient in _hyperionClients ) + try { - try + byte[] imageData = _screenCapture.Capture(); + foreach (HyperionClient hyperionClient in _hyperionClients) { - byte[] imageData = _screenCapture.Capture(); hyperionClient.SendImageData(imageData, _screenCapture.CaptureWidth, _screenCapture.CaptureHeight); - - // Uncomment the following to enable debugging - // MiscUtils.SaveRGBArrayToImageFile(imageData, _screenCapture.CaptureWidth, _screenCapture.CaptureHeight, AppConstants.DEBUG_IMAGE_FILE_NAME); - } - catch ( Exception ex ) - { - throw new Exception("Error occured while sending image to server: " + ex.Message, ex); } } + catch (CapturePausedException) + { + // Re-throw to be handled in StartCapture() + throw; + } + catch (Exception ex) + { + LOG.Error("Error occurred while capturing or sending image to server: " + ex.Message, ex); + throw; + } } private void StartCapture() { - InstantiateScreenCapture(); - InstantiateHyperionClients(); int captureAttempt = 1; - while ( CaptureEnabled ) + while (CaptureEnabled) { - try // This block will help retry capture before giving up + try { + // Ensure screen capture and clients are instantiated + if (_screenCapture == null || _screenCapture.IsDisposed()) + { + InstantiateScreenCapture(); + } + if (_hyperionClients == null || _hyperionClients.Count == 0) + { + InstantiateHyperionClients(); + } + InitScreenCapture(); ConnectHyperionClients(); - TransmitNextFrame(); - _screenCapture.DelayNextCapture(); - captureAttempt = 1; // Reset attempt count + captureAttempt = 1; // Reset capture attempt counter after successful initialization + + while (CaptureEnabled) + { + TransmitNextFrame(); + _screenCapture.DelayNextCapture(); + } + } + catch (CapturePausedException ex) + { + LOG.Warn($"{this}: Capture paused: {ex.Message}. Disposing and preparing to reinitialize."); + _screenCapture?.Dispose(); + _screenCapture = null; + + DisposeHyperionClients(); + Thread.Sleep(AppConstants.CAPTURE_PAUSED_RETRY_DELAY); // Prevent rapid looping } - catch ( Exception ex ) + catch (Exception ex) { LOG.Error($"{this}: Exception in screen capture attempt: {captureAttempt}", ex); - if ( captureAttempt > AppConstants.REINIT_CAPTURE_AFTER_ATTEMPTS ) + if (captureAttempt >= AppConstants.REINIT_CAPTURE_AFTER_ATTEMPTS) { - // After a few attempt, try disposing screen capture object as well + LOG.Info($"{this}: Disposing resources and preparing to reinitialize."); _screenCapture?.Dispose(); - LOG.Info($"{this}: Will re-initialize screen capture on retry"); - } - if ( ++captureAttempt == AppConstants.MAX_CAPTURE_ATTEMPTS ) - { - LOG.Error($"{this}: Max screen capture attempts reached. Giving up."); - _notificationUtils.Error(ex.Message); - CaptureEnabled = false; + _screenCapture = null; + DisposeHyperionClients(); + captureAttempt = 1; // Reset capture attempt counter } else { - LOG.Info($"{this}: Waiting before next screen capture attempt"); Thread.Sleep(AppConstants.CAPTURE_FAILED_COOLDOWN_MILLIS); + captureAttempt++; } } } } + public void RestartCapture() + { + DisableCapture(); + Thread.Sleep(1000); // Wait a bit before restarting + EnableCapture(); + } + + private void RecreateScreenCaptureAndReconnect() + { + LOG.Info($"{this}: Recreating screen capture and reconnecting to Hyperion"); + + // Dispose of existing resources + _screenCapture?.Dispose(); + _screenCapture = null; + + foreach (var client in _hyperionClients) + { + client.Dispose(); + } + _hyperionClients.Clear(); + + // Wait a bit before reconnecting + Thread.Sleep(2000); + + // Let StartCapture handle the reinitialization + } + private void TryStartCapture() { try // Properly dispose everything object when turning off capture @@ -207,11 +276,19 @@ private void TryStartCapture() public void EnableCapture() { LOG.Info($"{this}: Enabling screen capture"); - CaptureEnabled = true; - _captureThread = new Thread(TryStartCapture) { IsBackground = true }; - _captureThread.Start(); + if (_captureThread == null || !_captureThread.IsAlive) + { + CaptureEnabled = true; + _captureThread = new Thread(TryStartCapture) { IsBackground = true }; + _captureThread.Start(); + } + else + { + LOG.Warn($"{this}: Capture thread is already running"); + } } + public void DisableCapture() { LOG.Info($"{this}: Disabling screen capture"); diff --git a/HyperionScreenCap/Helper/UpdateChecker.cs b/HyperionScreenCap/Helper/UpdateChecker.cs index 32d9234..195653f 100644 --- a/HyperionScreenCap/Helper/UpdateChecker.cs +++ b/HyperionScreenCap/Helper/UpdateChecker.cs @@ -24,14 +24,14 @@ class UpdateChecker public UpdateChecker() { _restClient = new RestClient(GITHUB_API_BASE_URL); - RestRequest request = new RestRequest(GITHUB_LATEST_RELEASE_GET_URL, Method.GET); - IRestResponse response = _restClient.Execute(request); + RestRequest request = new RestRequest(GITHUB_LATEST_RELEASE_GET_URL, Method.Get); // Updated this line + RestResponse response = _restClient.Execute(request); // Updated this line LatestRelease = response.Data; } public bool IsUpdateAvailable() { - if ( LatestRelease != null ) + if (LatestRelease != null) { Version currVer = Assembly.GetExecutingAssembly().GetName().Version; Version newVer; @@ -39,12 +39,12 @@ public bool IsUpdateAvailable() { newVer = new Version(LatestRelease.tag_name.Replace(TAG_NAME_PREFIX, "")); } - catch ( Exception ex ) + catch (Exception ex) { LOG.Error($"Tag name ({LatestRelease.tag_name}) for the latest release has an unexpected format", ex); newVer = ZERO_VERSION; // Fall back on 0.0 } - if ( newVer > currVer ) + if (newVer > currVer) return true; } return false; @@ -54,7 +54,7 @@ public static void StartUpdateCheck(bool isStartupCheck) { LOG.Info("Starting update check"); UpdateChecker updateChecker = new UpdateChecker(); - if ( updateChecker.IsUpdateAvailable() ) + if (updateChecker.IsUpdateAvailable()) { Release latestRelease = updateChecker.LatestRelease; StringBuilder bodyBuilder = new StringBuilder(); @@ -66,7 +66,7 @@ public static void StartUpdateCheck(bool isStartupCheck) bodyBuilder.Append("Would you like to download the update?"); DialogResult dialogResult = MessageBox.Show(bodyBuilder.ToString(), "Hyperion Screen Capture Update Available", MessageBoxButtons.YesNo, MessageBoxIcon.Information); - if ( dialogResult == DialogResult.Yes ) + if (dialogResult == DialogResult.Yes) { LOG.Info("Starting latest release download"); Process.Start(latestRelease.assets[0].browser_download_url); @@ -74,7 +74,7 @@ public static void StartUpdateCheck(bool isStartupCheck) } else { - if ( !isStartupCheck ) + if (!isStartupCheck) { MessageBox.Show("No updates available. If you think this is an error, please check your internet connection.", "Hyperion Screen Capture Update Check", MessageBoxButtons.OK); diff --git a/HyperionScreenCap/HyperionScreenCap.csproj b/HyperionScreenCap/HyperionScreenCap.csproj index b45527b..b305111 100644 --- a/HyperionScreenCap/HyperionScreenCap.csproj +++ b/HyperionScreenCap/HyperionScreenCap.csproj @@ -104,8 +104,8 @@ ..\packages\Markdig.0.22.0\lib\net452\Markdig.dll - - ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll ..\packages\Microsoft.CodeAnalysis.Common.3.8.0\lib\netstandard2.0\Microsoft.CodeAnalysis.dll @@ -119,11 +119,11 @@ ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.3.8.0\lib\netstandard2.0\Microsoft.CodeAnalysis.Workspaces.dll - - ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - - ..\packages\RestSharp.106.11.7\lib\net452\RestSharp.dll + + ..\packages\RestSharp.112.0.0\lib\net48\RestSharp.dll ..\packages\SharpDX.4.2.0\lib\net45\SharpDX.dll @@ -141,8 +141,8 @@ ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - ..\packages\System.Collections.Immutable.5.0.0\lib\net461\System.Collections.Immutable.dll + + ..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll @@ -163,26 +163,36 @@ - - ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - ..\packages\System.Reflection.Metadata.5.0.0\lib\net461\System.Reflection.Metadata.dll + + ..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.Text.Encoding.CodePages.5.0.0\lib\net461\System.Text.Encoding.CodePages.dll + + ..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.8.0.4\lib\net462\System.Text.Json.dll + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + diff --git a/HyperionScreenCap/Networking/FbsClinet.cs b/HyperionScreenCap/Networking/FbsClinet.cs index eebe44d..228a5d8 100644 --- a/HyperionScreenCap/Networking/FbsClinet.cs +++ b/HyperionScreenCap/Networking/FbsClinet.cs @@ -1,6 +1,7 @@ using System; using hyperionnet; using FlatBuffers; +using static Humanizer.In; namespace HyperionScreenCap.Networking { @@ -55,11 +56,29 @@ private void SendFinishedMessage(FlatBufferBuilder finaliedBuilder) var messageSize = messageToSend.Length; sendMessageSize(messageSize); _stream.Write(messageToSend, 0, messageSize); + _stream.Flush(); // Ensure data is sent immediately } public override String ToString() { return $"FbsClinet[{_host}:{_port} ({_priority})]"; } + + protected override void SendRegistrationMessage() + { + var builder = new FlatBufferBuilder(64); + var originOffset = builder.CreateString("HyperionScreenCap"); + var registerOffset = Register.CreateRegister(builder, originOffset, _priority); + var requestOffset = Request.CreateRequest(builder, Command.Register, registerOffset.Value); + builder.Finish(requestOffset.Value); + SendFinishedMessage(builder); + } + + public override void SendInitialFrame(int width, int height) + { + // Send a black frame to initialize the connection + byte[] blackFrame = new byte[width * height * 3]; + SendImageDataMessage(blackFrame, width, height); + } } } diff --git a/HyperionScreenCap/Networking/HyperionClient.cs b/HyperionScreenCap/Networking/HyperionClient.cs index 54008a0..47d7676 100644 --- a/HyperionScreenCap/Networking/HyperionClient.cs +++ b/HyperionScreenCap/Networking/HyperionClient.cs @@ -30,33 +30,33 @@ public HyperionClient(string host, int port, int priority, int messageDuration) _messageDuration = messageDuration; } - public void Connect() + protected abstract void SendRegistrationMessage(); + public abstract void SendInitialFrame(int width, int height); + + public virtual void Connect() { - if ( _initLock || IsConnected() ) - { - LOG.Info($"{this} already connected. Skipping request."); - return; - } _initLock = true; LOG.Info($"{this} Init lock set"); - _socket = new TcpClient - { - SendTimeout = AppConstants.PROTO_CLIENT_SOCKET_TIMEOUT, - ReceiveTimeout = AppConstants.PROTO_CLIENT_SOCKET_TIMEOUT - }; - try { + _socket = new TcpClient + { + SendTimeout = AppConstants.PROTO_CLIENT_SOCKET_TIMEOUT, + ReceiveTimeout = AppConstants.PROTO_CLIENT_SOCKET_TIMEOUT + }; + _socket.Connect(_host, _port); _stream = _socket.GetStream(); + Initialized = true; + + SendRegistrationMessage(); // Moved here to ensure it's called after connecting } finally { _initLock = false; LOG.Info($"{this} Init lock unset"); } - Initialized = true; } public bool IsConnected() diff --git a/HyperionScreenCap/Networking/ProtoClient.cs b/HyperionScreenCap/Networking/ProtoClient.cs index b8e022c..38917d0 100644 --- a/HyperionScreenCap/Networking/ProtoClient.cs +++ b/HyperionScreenCap/Networking/ProtoClient.cs @@ -1,6 +1,7 @@ using System; using Google.ProtocolBuffers; using proto; +using static Humanizer.In; namespace HyperionScreenCap.Networking { @@ -78,5 +79,22 @@ public override String ToString() { return $"ProtoClient[{_host}:{_port} ({_priority})]"; } + + protected override void SendRegistrationMessage() + { + // Assuming there's no specific RegisterRequest, we'll use the general HyperionRequest + var request = proto.HyperionRequest.CreateBuilder() + .SetCommand(proto.HyperionRequest.Types.Command.COLOR) // Use an existing command + .Build(); + + SendRequest(request); + } + + public override void SendInitialFrame(int width, int height) + { + // Send a black frame to initialize the connection + byte[] blackFrame = new byte[width * height * 3]; + SendImageDataMessage(blackFrame, width, height); + } } } diff --git a/HyperionScreenCap/Networking/fbs/RawImage.cs b/HyperionScreenCap/Networking/fbs/RawImage.cs index 3757751..8ff802a 100644 --- a/HyperionScreenCap/Networking/fbs/RawImage.cs +++ b/HyperionScreenCap/Networking/fbs/RawImage.cs @@ -33,20 +33,40 @@ public struct RawImage : IFlatbufferObject public int Height { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)-1; } } public bool MutateHeight(int height) { int o = __p.__offset(8); if (o != 0) { __p.bb.PutInt(o + __p.bb_pos, height); return true; } else { return false; } } - public static Offset CreateRawImage(FlatBufferBuilder builder, - VectorOffset dataOffset = default(VectorOffset), - int width = -1, - int height = -1) { +public static Offset CreateRawImage(FlatBufferBuilder builder, + VectorOffset dataOffset = default(VectorOffset), + int width = -1, + int height = -1) +{ builder.StartTable(3); RawImage.AddHeight(builder, height); RawImage.AddWidth(builder, width); - RawImage.AddData(builder, dataOffset); + if (dataOffset.Value != 0) // Check if data is not null + { + RawImage.AddData(builder, dataOffset); + } return RawImage.EndRawImage(builder); - } +} public static void StartRawImage(FlatBufferBuilder builder) { builder.StartTable(3); } public static void AddData(FlatBufferBuilder builder, VectorOffset dataOffset) { builder.AddOffset(0, dataOffset.Value, 0); } - public static VectorOffset CreateDataVector(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte(data[i]); return builder.EndVector(); } + + public static VectorOffset CreateDataVector(FlatBufferBuilder builder, byte[] data) + { + if (data == null || data.Length == 0) // Handle null or empty data case + { + // Return an empty vector if data is null or empty to avoid crashing + return builder.EndVector(); + } + + builder.StartVector(1, data.Length, 1); + for (int i = data.Length - 1; i >= 0; i--) + { + builder.AddByte(data[i]); + } + return builder.EndVector(); + } + public static VectorOffset CreateDataVectorBlock(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); builder.Add(data); return builder.EndVector(); } public static void StartDataVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); } public static void AddWidth(FlatBufferBuilder builder, int width) { builder.AddInt(1, width, -1); } diff --git a/HyperionScreenCap/Properties/AssemblyInfo.cs b/HyperionScreenCap/Properties/AssemblyInfo.cs index f80e7b0..882b663 100644 --- a/HyperionScreenCap/Properties/AssemblyInfo.cs +++ b/HyperionScreenCap/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.9.0.0")] +[assembly: AssemblyVersion("2.9.0.3")] //[assembly: AssemblyFileVersion("2.0.0.0")] Commented out so that it will be generated automatically diff --git a/HyperionScreenCap/app.config b/HyperionScreenCap/app.config index 36c3a75..fe1e587 100644 --- a/HyperionScreenCap/app.config +++ b/HyperionScreenCap/app.config @@ -106,7 +106,7 @@ - + @@ -114,7 +114,7 @@ - + @@ -126,12 +126,20 @@ - + + + + + + + + + diff --git a/HyperionScreenCap/packages.config b/HyperionScreenCap/packages.config index e3389c4..f1548ab 100644 --- a/HyperionScreenCap/packages.config +++ b/HyperionScreenCap/packages.config @@ -8,30 +8,33 @@ - + - - + + - + - + - - + + + + + \ No newline at end of file