22using System . Threading . Channels ;
33
44using Devolutions . NowClient . Worker ;
5+ using Devolutions . NowProto ;
56using Devolutions . NowProto . Capabilities ;
67using Devolutions . NowProto . Messages ;
78
@@ -64,7 +65,9 @@ public static async Task<NowClient> Connect(INowTransport transportImpl)
6465 }
6566 } ) ;
6667
67- return new NowClient ( capabilities , workerTask , clientChannel . Writer ) ;
68+ var client = new NowClient ( capabilities , workerTask , clientChannel . Writer ) ;
69+
70+ return client ;
6871 }
6972
7073 /// <summary>
@@ -336,6 +339,157 @@ public async Task ForceTermiate()
336339 await _commandWriter . WriteAsync ( new CommandChannelClose ( ) ) ;
337340 }
338341
342+ // RDM Methods
343+
344+ /// <summary>
345+ /// Sets the callback for RDM application notifications.
346+ /// </summary>
347+ public async Task SetRdmAppNotifyHandler ( RdmAppNotifyHandler ? handler )
348+ {
349+ ThrowIfWorkerTerminated ( ) ;
350+
351+ await _commandWriter . WriteAsync ( new CommandSetRdmAppNotifyHandler ( handler ) ) ;
352+ }
353+
354+ private bool _rdmCapabilitiesSent = false ;
355+ private RdmCapabilityInfo ? _rdmCapabilities = null ;
356+
357+ /// <summary>
358+ /// Performs RDM capabilities exchange with the server.
359+ /// This method is automatically called before any RDM app or session operations if not already called.
360+ /// </summary>
361+ public async Task RdmSync ( )
362+ {
363+ ThrowIfWorkerTerminated ( ) ;
364+
365+ var clientTimestamp = DateTimeOffset . UtcNow ;
366+ var message = new NowMsgRdmCapabilities . Builder (
367+ ( ulong ) clientTimestamp . ToUnixTimeSeconds ( ) ,
368+ ""
369+ ) . Build ( ) ;
370+
371+ var responseHandler = new TaskCompletionSource < NowMsgRdmCapabilities > ( ) ;
372+ var command = new CommandRdmCapabilities ( message , responseHandler ) ;
373+
374+ await _commandWriter . WriteAsync ( command ) ;
375+
376+ var response = await responseHandler . Task . WaitAsync ( TimeSpan . FromSeconds ( 10 ) ) ;
377+
378+ _rdmCapabilities = new RdmCapabilityInfo
379+ {
380+ IsAppAvailable = response . IsAppAvailable ,
381+ RdmVersion = response . RdmVersion ,
382+ VersionExtra = response . VersionExtra ,
383+ ServerTimestamp = DateTimeOffset . FromUnixTimeSeconds ( ( long ) response . Timestamp ) ,
384+ } ;
385+
386+ _rdmCapabilitiesSent = true ;
387+ }
388+
389+ /// <summary>
390+ /// Checks if RDM application is available on the server.
391+ /// Automatically performs capabilities exchange if not already done.
392+ /// </summary>
393+ public async Task < bool > IsRdmAppAvailable ( )
394+ {
395+ await EnsureRdmCapabilitiesSent ( ) ;
396+
397+ return _rdmCapabilities ? . IsAppAvailable ?? false ;
398+ }
399+
400+ /// <summary>
401+ /// Gets the RDM version information from the server.
402+ /// Automatically performs capabilities exchange if not already done.
403+ /// </summary>
404+ public async Task < RdmVersion ? > GetRdmVersion ( )
405+ {
406+ await EnsureRdmCapabilitiesSent ( ) ;
407+
408+ if ( _rdmCapabilities == null || ! _rdmCapabilities . IsAppAvailable )
409+ return null ;
410+
411+ return new RdmVersion ( _rdmCapabilities . RdmVersion ,
412+ string . IsNullOrEmpty ( _rdmCapabilities . VersionExtra ) ? null : _rdmCapabilities . VersionExtra ) ;
413+ }
414+
415+ /// <summary>
416+ /// Starts the RDM application on the server.
417+ /// Automatically performs capabilities exchange if not already done.
418+ /// </summary>
419+ public async Task RdmStart ( RdmStartParams ? startParams = null )
420+ {
421+ ThrowIfWorkerTerminated ( ) ;
422+ await EnsureRdmCapabilitiesSent ( ) ;
423+
424+ var parameters = startParams ?? new RdmStartParams ( ) ;
425+ var builder = new NowMsgRdmAppStart . Builder ( )
426+ . Timeout ( ( uint ) parameters . Timeout . TotalSeconds ) ;
427+
428+ if ( parameters . LaunchFlags . HasFlag ( NowRdmLaunchFlags . JumpMode ) )
429+ builder = builder . WithJumpMode ( ) ;
430+ if ( parameters . LaunchFlags . HasFlag ( NowRdmLaunchFlags . Maximized ) )
431+ builder = builder . WithMaximized ( ) ;
432+ if ( parameters . LaunchFlags . HasFlag ( NowRdmLaunchFlags . Fullscreen ) )
433+ builder = builder . WithFullscreen ( ) ;
434+
435+ var message = builder . Build ( ) ;
436+ var command = new CommandRdmAppStart ( message ) ;
437+
438+ await _commandWriter . WriteAsync ( command ) ;
439+ }
440+
441+ /// <summary>
442+ /// Sends an action command to the RDM application.
443+ /// Automatically performs capabilities exchange if not already done.
444+ /// </summary>
445+ public async Task RdmAction ( NowRdmAppAction action , string actionData = "" )
446+ {
447+ ThrowIfWorkerTerminated ( ) ;
448+ await EnsureRdmCapabilitiesSent ( ) ;
449+
450+ var message = new NowMsgRdmAppAction ( action , actionData ) ;
451+ var command = new CommandRdmAppAction ( message ) ;
452+
453+ await _commandWriter . WriteAsync ( command ) ;
454+ }
455+
456+ /// <summary>
457+ /// Starts a new RDM session.
458+ /// Automatically performs capabilities exchange if not already done.
459+ /// </summary>
460+ public async Task < RdmSession > RdmSessionStart ( RdmSessionStartParams sessionParams )
461+ {
462+ ThrowIfWorkerTerminated ( ) ;
463+ await EnsureRdmCapabilitiesSent ( ) ;
464+
465+ var message = new NowMsgRdmSessionStart ( sessionParams . SessionId , sessionParams . ConnectionId , sessionParams . ConnectionData ) ;
466+ var session = new RdmSession ( sessionParams . SessionId , _commandWriter , sessionParams . NotifyHandler ) ;
467+ var command = new CommandRdmSessionStart ( message , session ) ;
468+
469+ await _commandWriter . WriteAsync ( command ) ;
470+
471+ return session ;
472+ }
473+
474+ /// <summary>
475+ /// Ensures that RDM capabilities have been exchanged with the server.
476+ /// </summary>
477+ private async Task EnsureRdmCapabilitiesSent ( )
478+ {
479+ if ( ! _rdmCapabilitiesSent )
480+ {
481+ // Check if server version supports RDM functionality
482+ if ( Capabilities . Version < MIN_RDM_ENABLED_VERSION )
483+ {
484+ throw new NowClientException (
485+ $ "RDM functionality requires NOW-proto server version { MIN_RDM_ENABLED_VERSION . Major } .{ MIN_RDM_ENABLED_VERSION . Minor } or higher. " +
486+ $ "Current server version is { Capabilities . Version . Major } .{ Capabilities . Version . Minor } .") ;
487+ }
488+
489+ await RdmSync ( ) ;
490+ }
491+ }
492+
339493 private static void ThrowCapabilitiesError ( string capability )
340494 {
341495 throw new NowClientException ( $ "{ capability } is not supported by server.") ;
@@ -351,18 +505,24 @@ private void ThrowIfWorkerTerminated()
351505
352506 private NowClient (
353507 NowMsgChannelCapset capabilities ,
354- Task runnerTask ,
508+ Task workerTask ,
355509 ChannelWriter < IClientCommand > commandWriter
356510 )
357511 {
358512 this . Capabilities = capabilities ;
359- this . _runnerTask = runnerTask ;
513+ this . _runnerTask = workerTask ;
360514 this . _commandWriter = commandWriter ;
361515 }
362516
363517 private const int TimeoutConnectSeconds = 10 ;
364518 private const int IoChannelCapacity = 1024 ;
365519
520+ /// <summary>
521+ /// Minimum NOW-proto server version required for RDM functionality.
522+ /// RDM features were introduced in version 1.3, so servers with version 1.2 and below are not supported.
523+ /// </summary>
524+ private static readonly NowProtoVersion MIN_RDM_ENABLED_VERSION = new ( 1 , 3 ) ;
525+
366526 /// <summary>
367527 /// Check if the NOW-proto channel has been terminated.
368528 /// </summary>
0 commit comments