'use strict'; function getElectron() { try { return require('electron'); } catch (error) { throw new Error(`Electron APIs are unavailable: ${error.message}`); } } function normalizeText(value) { return String(value || '').trim().toLowerCase(); } function getAllWindows() { const electron = getElectron(); const BrowserWindow = electron.BrowserWindow; if (!BrowserWindow || typeof BrowserWindow.getAllWindows !== 'function') { throw new Error('Electron BrowserWindow API is unavailable.'); } return BrowserWindow.getAllWindows().filter((window) => window && !window.isDestroyed()); } function inferWindowKind(title) { const normalized = normalizeText(title); if (normalized.includes('simulator')) { return 'simulator'; } if (normalized.includes('preview')) { return 'preview'; } if (normalized.includes('cocos creator') || normalized.includes('cocos')) { return 'editor'; } return 'unknown'; } function listWindows() { return getAllWindows().map((window, index) => ({ index, id: typeof window.id === 'number' ? window.id : index, title: typeof window.getTitle === 'function' ? window.getTitle() : '', bounds: typeof window.getBounds === 'function' ? window.getBounds() : null, visible: typeof window.isVisible === 'function' ? window.isVisible() : true, focused: typeof window.isFocused === 'function' ? window.isFocused() : false, kind: inferWindowKind(typeof window.getTitle === 'function' ? window.getTitle() : ''), })); } function pickWindow(options = {}) { const electron = getElectron(); const BrowserWindow = electron.BrowserWindow; const windows = getAllWindows(); if (!windows.length) { throw new Error('No Electron windows are available.'); } const titleContains = normalizeText(options.titleContains); const windowKind = normalizeText(options.windowKind || 'focused'); const focusedWindow = BrowserWindow.getFocusedWindow && BrowserWindow.getFocusedWindow(); const titleMatches = (window) => { if (!titleContains) { return true; } return normalizeText(window.getTitle && window.getTitle()).includes(titleContains); }; const kindMatches = (window) => { const kind = inferWindowKind(window.getTitle && window.getTitle()); switch (windowKind) { case 'focused': return true; case 'editor': case 'simulator': case 'preview': return kind === windowKind; default: return true; } }; const candidates = windows.filter((window) => kindMatches(window) && titleMatches(window)); const target = (focusedWindow && candidates.includes(focusedWindow) && focusedWindow) || candidates.find((window) => typeof window.isVisible === 'function' ? window.isVisible() : true) || candidates[0] || windows[0]; if (!target) { throw new Error(`No BrowserWindow matched windowKind='${windowKind}' titleContains='${titleContains}'.`); } return target; } async function executeJavaScript(window, script) { if (!window || !window.webContents || typeof window.webContents.executeJavaScript !== 'function') { throw new Error('Target window does not support webContents.executeJavaScript.'); } return await window.webContents.executeJavaScript(script, true); } function buildPanelBoundsScript(panelName) { const panel = JSON.stringify(String(panelName || 'scene')); return ` (() => { const panelName = ${panel}.toLowerCase(); const results = []; const isVisible = (element) => { if (!element || typeof element.getBoundingClientRect !== 'function') return false; const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return style && style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 4 && rect.height > 4; }; const textOf = (element) => { const parts = [ element.getAttribute && element.getAttribute('name'), element.getAttribute && element.getAttribute('title'), element.id, element.className, element.textContent, ]; return parts.filter(Boolean).join(' ').toLowerCase(); }; const collectAll = (root, bucket) => { if (!root) return; const nodes = root.querySelectorAll ? root.querySelectorAll('*') : []; for (const node of nodes) { bucket.push(node); if (node.shadowRoot) { collectAll(node.shadowRoot, bucket); } } }; const all = []; collectAll(document, all); const scoreElement = (element) => { const text = textOf(element); let score = 0; if (text.includes(panelName)) score += 100; if (text.includes('panel-frame')) score += 5; if (panelName === 'scene' && text.includes('game')) score -= 10; if (panelName === 'game' && text.includes('scene')) score -= 10; return score; }; const largestChildRect = (element) => { const bucket = [element]; if (element.shadowRoot) collectAll(element.shadowRoot, bucket); const children = bucket .filter((node) => isVisible(node)) .map((node) => { const rect = node.getBoundingClientRect(); const text = textOf(node); let score = rect.width * rect.height; if (node.tagName && node.tagName.toLowerCase() === 'canvas') score += 50000; if (text.includes(panelName)) score += 20000; if (text.includes('canvas') || text.includes('preview') || text.includes('viewport')) score += 10000; return { rect, score, tag: node.tagName || '', text }; }) .sort((a, b) => b.score - a.score); return children[0] || null; }; const candidates = all .filter((element) => isVisible(element)) .map((element) => { const panelScore = scoreElement(element); if (panelScore <= 0) return null; const rectInfo = largestChildRect(element) || { rect: element.getBoundingClientRect(), score: 0, tag: element.tagName || '', text: textOf(element) }; return { score: panelScore + rectInfo.score, elementTag: element.tagName || '', rect: rectInfo.rect, text: textOf(element), innerTag: rectInfo.tag, innerText: rectInfo.text, }; }) .filter(Boolean) .sort((a, b) => b.score - a.score); const fallbackCanvases = all .filter((element) => isVisible(element) && element.tagName && element.tagName.toLowerCase() === 'canvas') .map((element) => { const rect = element.getBoundingClientRect(); const text = textOf(element); let score = rect.width * rect.height; if (text.includes(panelName)) score += 50000; return { score, rect, elementTag: 'CANVAS', text, innerTag: 'CANVAS', innerText: text }; }) .sort((a, b) => b.score - a.score); const target = candidates[0] || fallbackCanvases[0]; if (!target) return null; return { x: Math.max(0, Math.floor(target.rect.left)), y: Math.max(0, Math.floor(target.rect.top)), width: Math.max(1, Math.floor(target.rect.width)), height: Math.max(1, Math.floor(target.rect.height)), elementTag: target.elementTag, innerTag: target.innerTag, text: target.text, innerText: target.innerText, }; })(); `; } async function getPanelBounds(window, panelName) { const result = await executeJavaScript(window, buildPanelBoundsScript(panelName)); if (!result || !result.width || !result.height) { throw new Error(`Could not locate a visible '${panelName}' panel in the target window.`); } return result; } function buildPanelFocusScript(panelName, offsetX, offsetY) { return ` (() => { const panelName = ${JSON.stringify(String(panelName || 'scene').toLowerCase())}; const offsetX = ${Number(offsetX || 0)}; const offsetY = ${Number(offsetY || 0)}; const isVisible = (element) => { if (!element || typeof element.getBoundingClientRect !== 'function') return false; const style = window.getComputedStyle(element); const rect = element.getBoundingClientRect(); return style && style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 4 && rect.height > 4; }; const textOf = (element) => [ element.getAttribute && element.getAttribute('name'), element.getAttribute && element.getAttribute('title'), element.id, element.className, element.textContent, ].filter(Boolean).join(' ').toLowerCase(); const collectAll = (root, bucket) => { if (!root) return; const nodes = root.querySelectorAll ? root.querySelectorAll('*') : []; for (const node of nodes) { bucket.push(node); if (node.shadowRoot) collectAll(node.shadowRoot, bucket); } }; const all = []; collectAll(document, all); const target = all.find((element) => isVisible(element) && textOf(element).includes(panelName)); const rect = target ? target.getBoundingClientRect() : { left: 0, top: 0, width: window.innerWidth, height: window.innerHeight }; const x = Math.floor(rect.left + rect.width / 2 + offsetX); const y = Math.floor(rect.top + rect.height / 2 + offsetY); const focusable = document.elementFromPoint(x, y) || target || document.body; if (focusable && typeof focusable.focus === 'function') focusable.focus(); return { x, y }; })(); `; } async function getPanelPoint(window, panelName, offsetX, offsetY) { return await executeJavaScript(window, buildPanelFocusScript(panelName, offsetX, offsetY)); } function sleep(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } module.exports = { executeJavaScript, getAllWindows, getPanelBounds, getPanelPoint, listWindows, pickWindow, sleep, };