@@ -13,6 +13,7 @@ import { VideoRecorder } from './videoRecorder.js';
1313import type { VideoRecordOptions } from './videoRecorder.js' ;
1414import { safeHandle , getMainWindowId } from './ipcUtils.js' ;
1515import { logger } from './logger.js' ;
16+ import { scoreAndFilterWindows } from './windowScorer.js' ;
1617
1718// ================================================================
1819// 模块级状态
@@ -34,6 +35,14 @@ const START_DEBOUNCE_MS = 500;
3435/** 单调递增会话标识,用于防止 stop 后残留的 debounce 重启采集 */
3536let captureSessionToken = 0 ;
3637
38+ /** 窗口监听轮询定时器 */
39+ let windowWatchTimer : ReturnType < typeof setInterval > | null = null ;
40+
41+ /** 上一次窗口列表的 id 集合(用于 diff 检测变化) */
42+ let lastWindowIds : Set < string > = new Set ( ) ;
43+
44+ const WINDOW_WATCH_INTERVAL_MS = 3000 ;
45+
3746// ================================================================
3847// 公共 API
3948// ================================================================
@@ -120,21 +129,81 @@ export function registerCaptureHandlers(): void {
120129 thumbnailSize : { width : 240 , height : 135 } ,
121130 } ) ;
122131
123- return sources . map ( ( src ) => {
124- // 压缩缩略图,避免窗口多时 UI 内存占用过大
132+ const rawWindows = sources . map ( ( src ) => {
125133 const thumb = src . thumbnail . isEmpty ( ) ? src . thumbnail : src . thumbnail . resize ( { width : 120 } ) ;
126134 return {
127135 id : src . id ,
128136 title : src . name ,
129137 thumbnail : thumb . toDataURL ( ) ,
130138 } ;
131139 } ) ;
140+
141+ // 智能评分、过滤与排序
142+ return scoreAndFilterWindows ( rawWindows ) ;
132143 } catch ( err ) {
133144 logger . error ( '[IPC] screen_list_windows failed:' , err ) ;
134145 return [ ] ;
135146 }
136147 } ) ;
137148
149+ // ---- 窗口变化监听(轮询) ----
150+
151+ safeHandle ( 'screen_watch_windows_start' , async ( ) => {
152+ if ( windowWatchTimer ) return { success : true } ; // 已在监听
153+
154+ logger . info ( '[IPC] 窗口监听已启动, interval=' + WINDOW_WATCH_INTERVAL_MS + 'ms' ) ;
155+
156+ windowWatchTimer = setInterval ( async ( ) => {
157+ try {
158+ const sources = await desktopCapturer . getSources ( {
159+ types : [ 'window' ] ,
160+ thumbnailSize : { width : 240 , height : 135 } ,
161+ } ) ;
162+
163+ const currentIds = new Set ( sources . map ( ( s ) => s . id ) ) ;
164+
165+ // 检测是否有变化(新增或关闭窗口)
166+ const hasChanged =
167+ currentIds . size !== lastWindowIds . size ||
168+ [ ...currentIds ] . some ( ( id ) => ! lastWindowIds . has ( id ) ) ;
169+
170+ if ( hasChanged ) {
171+ lastWindowIds = currentIds ;
172+
173+ const rawWindows = sources . map ( ( src ) => {
174+ const thumb = src . thumbnail . isEmpty ( ) ? src . thumbnail : src . thumbnail . resize ( { width : 120 } ) ;
175+ return { id : src . id , title : src . name , thumbnail : thumb . toDataURL ( ) } ;
176+ } ) ;
177+
178+ const scored = scoreAndFilterWindows ( rawWindows ) ;
179+
180+ // 推送到渲染进程
181+ const mainWindowId = getMainWindowId ( ) ;
182+ if ( mainWindowId ) {
183+ const win = BrowserWindow . fromId ( mainWindowId ) ;
184+ if ( win && ! win . isDestroyed ( ) ) {
185+ win . webContents . send ( 'screen_windows_changed' , scored ) ;
186+ }
187+ }
188+ }
189+ } catch ( err ) {
190+ logger . error ( '[IPC] window watch poll error:' , err ) ;
191+ }
192+ } , WINDOW_WATCH_INTERVAL_MS ) ;
193+
194+ return { success : true } ;
195+ } ) ;
196+
197+ safeHandle ( 'screen_watch_windows_stop' , async ( ) => {
198+ if ( windowWatchTimer ) {
199+ clearInterval ( windowWatchTimer ) ;
200+ windowWatchTimer = null ;
201+ lastWindowIds = new Set ( ) ;
202+ logger . info ( '[IPC] 窗口监听已停止' ) ;
203+ }
204+ return { success : true } ;
205+ } ) ;
206+
138207 // ---- 系统音频捕获 ----
139208
140209 safeHandle ( 'audio_list_sources' , async ( ) => {
0 commit comments