@@ -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 ) ;
@@ -73,7 +74,7 @@ impl Task for DvcIoTask {
7374
7475 // Spawning thread is relatively short operation, so it could be executed synchronously.
7576 let io_thread = std:: thread:: spawn ( move || {
76- let io_thread_result = run_dvc_io ( write_rx, read_tx, cloned_shutdown_event) ;
77+ let io_thread_result: Result < ( ) , anyhow :: Error > = run_dvc_io ( write_rx, read_tx, cloned_shutdown_event) ;
7778
7879 if let Err ( error) = io_thread_result {
7980 error ! ( %error, "DVC IO thread failed" ) ;
@@ -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,8 @@ 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 < ( ) > > ,
292301}
293302
294303impl MessageProcessor {
@@ -302,6 +311,7 @@ impl MessageProcessor {
302311 io_notification_tx,
303312 capabilities,
304313 sessions : HashMap :: new ( ) ,
314+ window_monitor_shutdown_tx : None ,
305315 }
306316 }
307317
@@ -466,6 +476,14 @@ impl MessageProcessor {
466476 error ! ( %error, "Failed to set keyboard layout" ) ;
467477 }
468478 }
479+ NowMessage :: Session ( NowSessionMessage :: WindowRecStart ( start_msg) ) => {
480+ if let Err ( error) = self . start_window_recording ( start_msg) . await {
481+ error ! ( %error, "Failed to start window recording" ) ;
482+ }
483+ }
484+ NowMessage :: Session ( NowSessionMessage :: WindowRecStop ( _stop_msg) ) => {
485+ self . stop_window_recording ( ) ;
486+ }
469487 NowMessage :: System ( NowSystemMessage :: Shutdown ( shutdown_msg) ) => {
470488 let mut current_process_token = win_api_wrappers:: process:: Process :: current_process ( )
471489 . token ( TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY ) ?;
@@ -742,6 +760,46 @@ impl MessageProcessor {
742760
743761 self . sessions . clear ( ) ;
744762 }
763+
764+ async fn start_window_recording ( & mut self , start_msg : NowSessionWindowRecStartMsg ) -> anyhow:: Result < ( ) > {
765+ // Stop any existing window recording first.
766+ self . stop_window_recording ( ) ;
767+
768+ info ! ( "Starting window recording" ) ;
769+
770+ let poll_interval_ms = if start_msg. poll_interval > 0 {
771+ u64:: from ( start_msg. poll_interval )
772+ } else {
773+ 1000 // Default to 1000ms (1 second) if not specified.
774+ } ;
775+
776+ let track_title_changes = start_msg. flags . contains ( WindowRecStartFlags :: TRACK_TITLE_CHANGE ) ;
777+
778+ // Create shutdown channel for window monitor.
779+ let ( shutdown_tx, shutdown_rx) = tokio:: sync:: oneshot:: channel ( ) ;
780+
781+ // Store shutdown sender so we can stop monitoring later.
782+ self . window_monitor_shutdown_tx = Some ( shutdown_tx) ;
783+
784+ // Spawn window monitor task.
785+ let event_tx = self . io_notification_tx . clone ( ) ;
786+ tokio:: task:: spawn ( async move {
787+ let config = WindowMonitorConfig :: new ( event_tx, track_title_changes, shutdown_rx)
788+ . with_poll_interval_ms ( poll_interval_ms) ;
789+
790+ run_window_monitor ( config) . await ;
791+ } ) ;
792+
793+ Ok ( ( ) )
794+ }
795+
796+ fn stop_window_recording ( & mut self ) {
797+ if let Some ( shutdown_tx) = self . window_monitor_shutdown_tx . take ( ) {
798+ info ! ( "Stopping window recording" ) ;
799+ // Send shutdown signal (ignore errors if receiver was already dropped).
800+ let _ = shutdown_tx. send ( ( ) ) ;
801+ }
802+ }
745803}
746804
747805fn append_ps_args ( args : & mut Vec < String > , msg : & NowExecWinPsMsg < ' _ > ) {
@@ -919,6 +977,15 @@ fn make_generic_error_failsafe(session_id: u32, code: u32, message: String) -> N
919977 } )
920978}
921979
980+ async fn handle_window_recording_event (
981+ dvc_tx : & WinapiSignaledSender < NowMessage < ' static > > ,
982+ message : NowSessionWindowRecEventMsg < ' static > ,
983+ ) -> anyhow:: Result < ( ) > {
984+ dvc_tx. send ( NowMessage :: from ( message. into_owned ( ) ) ) . await ?;
985+
986+ Ok ( ( ) )
987+ }
988+
922989async fn handle_exec_error ( dvc_tx : & WinapiSignaledSender < NowMessage < ' static > > , session_id : u32 , error : ExecError ) {
923990 let msg = match error {
924991 ExecError :: NowStatus ( status) => {
0 commit comments