@@ -27,8 +27,8 @@ use now_proto_pdu::{
2727 NowExecBatchMsg , NowExecCancelRspMsg , NowExecCapsetFlags , NowExecDataMsg , NowExecDataStreamKind , NowExecMessage ,
2828 NowExecProcessMsg , NowExecPwshMsg , NowExecResultMsg , NowExecRunMsg , NowExecStartedMsg , NowExecWinPsMsg , NowMessage ,
2929 NowMsgBoxResponse , NowProtoError , NowProtoVersion , NowSessionCapsetFlags , NowSessionMessage ,
30- NowSessionMsgBoxReqMsg , NowSessionMsgBoxRspMsg , NowStatusError , NowSystemCapsetFlags , NowSystemMessage ,
31- SetKbdLayoutOption ,
30+ NowSessionMsgBoxReqMsg , NowSessionMsgBoxRspMsg , NowSessionWindowRecEventMsg , NowSessionWindowRecStartMsg ,
31+ NowStatusError , NowSystemCapsetFlags , NowSystemMessage , SetKbdLayoutOption , WindowRecStartFlags ,
3232} ;
3333use win_api_wrappers:: event:: Event ;
3434use win_api_wrappers:: security:: privilege:: ScopedPrivileges ;
@@ -38,6 +38,7 @@ use crate::dvc::channel::{WinapiSignaledSender, bounded_mpsc_channel, winapi_sig
3838use crate :: dvc:: fs:: TmpFileGuard ;
3939use crate :: dvc:: io:: run_dvc_io;
4040use crate :: dvc:: process:: { ExecError , ServerChannelEvent , WinApiProcess , WinApiProcessBuilder } ;
41+ use crate :: dvc:: window_monitor:: { WindowMonitorConfig , run_window_monitor} ;
4142
4243// One minute heartbeat interval by default
4344const DEFAULT_HEARTBEAT_INTERVAL : core:: time:: Duration = core:: time:: Duration :: from_secs ( 60 ) ;
@@ -229,6 +230,11 @@ async fn process_messages(
229230
230231 handle_exec_error( & dvc_tx, session_id, error) . await ;
231232 }
233+ ServerChannelEvent :: WindowRecordingEvent { message } => {
234+ if let Err ( error) = handle_window_recording_event( & dvc_tx, message) . await {
235+ error!( %error, "Failed to handle window recording event" ) ;
236+ }
237+ }
232238 ServerChannelEvent :: CloseChannel => {
233239 info!( "Received close channel notification, shutting down..." ) ;
234240
@@ -265,7 +271,8 @@ fn default_server_caps() -> NowChannelCapsetMsg {
265271 NowSessionCapsetFlags :: LOCK
266272 | NowSessionCapsetFlags :: LOGOFF
267273 | NowSessionCapsetFlags :: MSGBOX
268- | NowSessionCapsetFlags :: SET_KBD_LAYOUT ,
274+ | NowSessionCapsetFlags :: SET_KBD_LAYOUT
275+ | NowSessionCapsetFlags :: WINDOW_RECORDING ,
269276 )
270277 . with_exec_capset (
271278 NowExecCapsetFlags :: STYLE_RUN
@@ -289,6 +296,10 @@ struct MessageProcessor {
289296 #[ allow( dead_code) ] // Not yet used.
290297 capabilities : NowChannelCapsetMsg ,
291298 sessions : HashMap < u32 , WinApiProcess > ,
299+ /// Shutdown signal sender for window monitoring task.
300+ window_monitor_shutdown_tx : Option < tokio:: sync:: oneshot:: Sender < ( ) > > ,
301+ /// Handle for the window monitor task.
302+ window_monitor_handle : Option < tokio:: task:: JoinHandle < ( ) > > ,
292303}
293304
294305impl MessageProcessor {
@@ -302,6 +313,8 @@ impl MessageProcessor {
302313 io_notification_tx,
303314 capabilities,
304315 sessions : HashMap :: new ( ) ,
316+ window_monitor_shutdown_tx : None ,
317+ window_monitor_handle : None ,
305318 }
306319 }
307320
@@ -466,6 +479,14 @@ impl MessageProcessor {
466479 error ! ( %error, "Failed to set keyboard layout" ) ;
467480 }
468481 }
482+ NowMessage :: Session ( NowSessionMessage :: WindowRecStart ( start_msg) ) => {
483+ if let Err ( error) = self . start_window_recording ( start_msg) . await {
484+ error ! ( %error, "Failed to start window recording" ) ;
485+ }
486+ }
487+ NowMessage :: Session ( NowSessionMessage :: WindowRecStop ( _stop_msg) ) => {
488+ self . stop_window_recording ( ) . await ;
489+ }
469490 NowMessage :: System ( NowSystemMessage :: Shutdown ( shutdown_msg) ) => {
470491 let mut current_process_token = win_api_wrappers:: process:: Process :: current_process ( )
471492 . token ( TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY ) ?;
@@ -742,6 +763,55 @@ impl MessageProcessor {
742763
743764 self . sessions . clear ( ) ;
744765 }
766+
767+ async fn start_window_recording ( & mut self , start_msg : NowSessionWindowRecStartMsg ) -> anyhow:: Result < ( ) > {
768+ // Stop any existing window recording first.
769+ self . stop_window_recording ( ) . await ;
770+
771+ info ! ( "Starting window recording" ) ;
772+
773+ let poll_interval_ms = if start_msg. poll_interval > 0 {
774+ u64:: from ( start_msg. poll_interval )
775+ } else {
776+ 1000 // Default to 1000ms (1 second) if not specified.
777+ } ;
778+
779+ let track_title_changes = start_msg. flags . contains ( WindowRecStartFlags :: TRACK_TITLE_CHANGE ) ;
780+
781+ // Create shutdown channel for window monitor.
782+ let ( shutdown_tx, shutdown_rx) = tokio:: sync:: oneshot:: channel ( ) ;
783+
784+ // Store shutdown sender so we can stop monitoring later.
785+ self . window_monitor_shutdown_tx = Some ( shutdown_tx) ;
786+
787+ // Spawn window monitor task.
788+ let event_tx = self . io_notification_tx . clone ( ) ;
789+ let window_monitor_handle = tokio:: task:: spawn ( async move {
790+ let config = WindowMonitorConfig :: new ( event_tx, track_title_changes, shutdown_rx)
791+ . with_poll_interval_ms ( poll_interval_ms) ;
792+
793+ run_window_monitor ( config) . await ;
794+ } ) ;
795+
796+ self . window_monitor_handle = Some ( window_monitor_handle) ;
797+
798+ Ok ( ( ) )
799+ }
800+
801+ async fn stop_window_recording ( & mut self ) {
802+ if let Some ( shutdown_tx) = self . window_monitor_shutdown_tx . take ( ) {
803+ info ! ( "Stopping window recording" ) ;
804+ // Send shutdown signal (ignore errors if receiver was already dropped).
805+ let _ = shutdown_tx. send ( ( ) ) ;
806+
807+ // Wait for the task to finish.
808+ if let Some ( handle) = self . window_monitor_handle . take ( )
809+ && let Err ( error) = handle. await
810+ {
811+ error ! ( %error, "Window monitor task panicked" ) ;
812+ }
813+ }
814+ }
745815}
746816
747817fn append_ps_args ( args : & mut Vec < String > , msg : & NowExecWinPsMsg < ' _ > ) {
@@ -919,6 +989,15 @@ fn make_generic_error_failsafe(session_id: u32, code: u32, message: String) -> N
919989 } )
920990}
921991
992+ async fn handle_window_recording_event (
993+ dvc_tx : & WinapiSignaledSender < NowMessage < ' static > > ,
994+ message : NowSessionWindowRecEventMsg < ' static > ,
995+ ) -> anyhow:: Result < ( ) > {
996+ dvc_tx. send ( NowMessage :: from ( message. into_owned ( ) ) ) . await ?;
997+
998+ Ok ( ( ) )
999+ }
1000+
9221001async fn handle_exec_error ( dvc_tx : & WinapiSignaledSender < NowMessage < ' static > > , session_id : u32 , error : ExecError ) {
9231002 let msg = match error {
9241003 ExecError :: NowStatus ( status) => {
0 commit comments