@@ -352,15 +352,163 @@ function sampleCursorPoint() {
352352export function registerIpcHandlers (
353353 createEditorWindow : ( ) => void ,
354354 createSourceSelectorWindow : ( ) => BrowserWindow ,
355+ createCountdownOverlayWindow : ( ) => BrowserWindow ,
355356 getMainWindow : ( ) => BrowserWindow | null ,
356357 getSourceSelectorWindow : ( ) => BrowserWindow | null ,
358+ getCountdownOverlayWindow : ( ) => BrowserWindow | null ,
357359 onRecordingStateChange ?: ( recording : boolean , sourceName : string ) => void ,
358360 switchToHud ?: ( ) => void ,
359361) {
362+ const supportsWindowOpacity = process . platform !== "linux" ;
363+ const countdownOverlayState = {
364+ visible : false ,
365+ value : null as number | null ,
366+ activeRunId : null as number | null ,
367+ hideCommitId : 0 ,
368+ hideCommitTimer : null as ReturnType < typeof setTimeout > | null ,
369+ } ;
370+ const COUNTDOWN_OVERLAY_HIDE_DEBOUNCE_MS = 1200 ;
371+
372+ const clearCountdownOverlayHideCommit = ( ) => {
373+ if ( countdownOverlayState . hideCommitTimer ) {
374+ clearTimeout ( countdownOverlayState . hideCommitTimer ) ;
375+ countdownOverlayState . hideCommitTimer = null ;
376+ }
377+ } ;
378+
379+ const commitCountdownOverlayHide = ( win : BrowserWindow , hideCommitId : number ) => {
380+ if ( win . isDestroyed ( ) ) {
381+ return ;
382+ }
383+
384+ if ( countdownOverlayState . visible || countdownOverlayState . hideCommitId !== hideCommitId ) {
385+ return ;
386+ }
387+
388+ win . hide ( ) ;
389+ if ( supportsWindowOpacity ) {
390+ // Reset baseline opacity for the next show cycle.
391+ win . setOpacity ( 1 ) ;
392+ }
393+ } ;
394+
395+ const flushCountdownOverlayState = ( win : BrowserWindow ) => {
396+ if ( win . isDestroyed ( ) ) {
397+ return ;
398+ }
399+
400+ clearCountdownOverlayHideCommit ( ) ;
401+ win . webContents . send ( "countdown-overlay-value" , countdownOverlayState . value ) ;
402+ if ( ! countdownOverlayState . visible ) {
403+ return ;
404+ }
405+
406+ if ( win . isVisible ( ) ) {
407+ if ( supportsWindowOpacity ) {
408+ win . setOpacity ( 1 ) ;
409+ }
410+ return ;
411+ }
412+
413+ setTimeout ( ( ) => {
414+ if ( ! win . isDestroyed ( ) && countdownOverlayState . visible && ! win . isVisible ( ) ) {
415+ if ( supportsWindowOpacity ) {
416+ win . setOpacity ( 0 ) ;
417+ }
418+ win . showInactive ( ) ;
419+
420+ if ( supportsWindowOpacity ) {
421+ setTimeout ( ( ) => {
422+ if ( ! win . isDestroyed ( ) && countdownOverlayState . visible && win . isVisible ( ) ) {
423+ win . setOpacity ( 1 ) ;
424+ }
425+ } , 0 ) ;
426+ }
427+ }
428+ } , 16 ) ;
429+ } ;
430+
431+ ipcMain . handle ( "countdown-overlay-show" , ( _ , value : number , runId : number ) => {
432+ countdownOverlayState . activeRunId = runId ;
433+ countdownOverlayState . visible = true ;
434+ countdownOverlayState . value = value ;
435+
436+ const win = getCountdownOverlayWindow ( ) ?? createCountdownOverlayWindow ( ) ;
437+ if ( win . isDestroyed ( ) ) {
438+ return ;
439+ }
440+
441+ if ( win . webContents . isLoading ( ) ) {
442+ win . webContents . once ( "did-finish-load" , ( ) => {
443+ if ( ! win . isDestroyed ( ) ) {
444+ flushCountdownOverlayState ( win ) ;
445+ }
446+ } ) ;
447+ } else {
448+ flushCountdownOverlayState ( win ) ;
449+ }
450+ } ) ;
451+
452+ ipcMain . handle ( "countdown-overlay-set-value" , ( _ , value : number , runId : number ) => {
453+ if ( countdownOverlayState . activeRunId !== runId || ! countdownOverlayState . visible ) {
454+ return ;
455+ }
456+
457+ countdownOverlayState . value = value ;
458+
459+ const win = getCountdownOverlayWindow ( ) ;
460+ if ( ! win || win . isDestroyed ( ) ) {
461+ return ;
462+ }
463+
464+ if ( win . webContents . isLoading ( ) ) {
465+ return ;
466+ }
467+
468+ win . webContents . send ( "countdown-overlay-value" , value ) ;
469+ } ) ;
470+
471+ ipcMain . handle ( "countdown-overlay-hide" , ( _ , runId : number ) => {
472+ if ( countdownOverlayState . activeRunId !== runId ) {
473+ return ;
474+ }
475+
476+ countdownOverlayState . visible = false ;
477+ countdownOverlayState . hideCommitId += 1 ;
478+ const hideCommitId = countdownOverlayState . hideCommitId ;
479+ clearCountdownOverlayHideCommit ( ) ;
480+
481+ const win = getCountdownOverlayWindow ( ) ;
482+ if ( ! win || win . isDestroyed ( ) ) {
483+ countdownOverlayState . value = null ;
484+ return ;
485+ }
486+
487+ if ( supportsWindowOpacity ) {
488+ // Hide visually immediately to avoid hide/show compositor flashes on rapid restart.
489+ win . setOpacity ( 0 ) ;
490+ }
491+
492+ countdownOverlayState . value = null ;
493+ if ( ! win . webContents . isLoading ( ) ) {
494+ win . webContents . send ( "countdown-overlay-value" , countdownOverlayState . value ) ;
495+ }
496+
497+ if ( ! supportsWindowOpacity ) {
498+ win . hide ( ) ;
499+ return ;
500+ }
501+
502+ countdownOverlayState . hideCommitTimer = setTimeout ( ( ) => {
503+ countdownOverlayState . hideCommitTimer = null ;
504+ commitCountdownOverlayHide ( win , hideCommitId ) ;
505+ } , COUNTDOWN_OVERLAY_HIDE_DEBOUNCE_MS ) ;
506+ } ) ;
507+
360508 ipcMain . handle ( "switch-to-hud" , ( ) => {
361509 if ( switchToHud ) switchToHud ( ) ;
362510 } ) ;
363- ipcMain . handle ( "start-new-recording" , async ( ) => {
511+ ipcMain . handle ( "start-new-recording" , ( ) => {
364512 try {
365513 setCurrentRecordingSessionState ( null ) ;
366514 if ( switchToHud ) {
@@ -518,9 +666,8 @@ export function registerIpcHandlers(
518666 } ) ;
519667
520668 ipcMain . handle ( "read-binary-file" , async ( _ , inputPath : string ) => {
521- let normalizedPath : string | null = null ;
522669 try {
523- normalizedPath = normalizeVideoSourcePath ( inputPath ) ;
670+ const normalizedPath = normalizeVideoSourcePath ( inputPath ) ;
524671 if ( ! normalizedPath ) {
525672 return { success : false , message : "Invalid file path" } ;
526673 }
@@ -545,7 +692,6 @@ export function registerIpcHandlers(
545692 success : false ,
546693 message : "Failed to read binary file" ,
547694 error : String ( error ) ,
548- path : normalizedPath ,
549695 } ;
550696 }
551697 } ) ;
0 commit comments