UNPKG

@lynker-desktop/electron-window-manager

Version:

electron-window-manager

408 lines (405 loc) 12.5 kB
const eIpc = require('@lynker-desktop/electron-ipc/renderer'); /** * Electron 窗口管理器 - 渲染进程模块 * 提供窗口创建、管理、查询等功能 * * @author Lynker Desktop Team * @version 1.0.0 */ // ==================== 工具函数 ==================== /** * 获取 IPC 渲染器实例 * 优先从全局变量获取,回退到 require 方式 * * @returns IpcRenderer 实例或空对象 */ const getIpc = () => { try { // 优先从全局变量获取(预加载脚本注入) const globalIpc = window?.__ELECTRON_WINDOW_MANAGER__?.ipcRenderer; if (globalIpc) { return globalIpc; } // 回退到 require 方式 return window?.require('electron').ipcRenderer; } catch (error) { console.error('当前非桌面端环境, 请在桌面端中调用'); return {}; } }; /** * 获取 Electron Remote 模块 * 优先从全局变量获取,回退到 require 方式 * * @returns RemoteType 实例或空对象 */ const getRemote = () => { try { // 优先从全局变量获取(预加载脚本注入) const globalRemote = window?.__ELECTRON_WINDOW_MANAGER__?.remote; if (globalRemote) { return globalRemote; } // 回退到 require 方式 return window?.require('@electron/remote'); } catch (error) { console.error('获取 Remote 模块失败: ', error); return {}; } }; /** * 获取所有窗口的 BrowserView 列表 * 遍历所有窗口并收集其 BrowserView * * @returns BrowserView 数组 */ const getAllBrowserViews = () => { const remote = getRemote(); const allWindows = remote.BrowserWindow.getAllWindows(); const allBrowserViews = []; allWindows.forEach(window => { const views = window.getBrowserViews(); allBrowserViews.push(...views); }); return allBrowserViews; }; /** * 通过 WebContents 查找对应的 BrowserView * 如果找不到已挂载的 BrowserView,则创建虚拟的 BrowserView 对象 * * @param webContent - WebContents 实例 * @returns BVItem 实例 */ const getBrowserViewByWebContent = (webContent) => { const allBrowserViews = getAllBrowserViews(); const existingView = allBrowserViews.find(view => view.webContents === webContent); // 如果找到已挂载的 BrowserView,直接返回 if (existingView) { return existingView; } // 如果 WebContents 存在但未挂载到窗口,创建虚拟 BrowserView if (webContent && !webContent.isDestroyed?.()) { return createVirtualBrowserView(webContent); } return existingView; }; /** * 创建虚拟的 BrowserView 对象 * 用于处理未挂载到窗口的 WebContents * * @param webContent - WebContents 实例 * @returns 虚拟的 BVItem 对象 */ const createVirtualBrowserView = (webContent) => { const virtualView = { id: webContent.id, _id: webContent.id, _type: 'BV', _name: '', _extraData: '', webContents: webContent, // 代理方法,通过 IPC 调用主进程 getBounds: async () => { const result = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'borrowView_getBounds', data: { webContentId: webContent.id, options: {} } }); return result; }, setBounds: async (bounds) => { return await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'borrowView_setBounds', data: { webContentId: webContent.id, options: bounds } }); }, setAutoResize: async (autoResize) => { return await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'borrowView_setAutoResize', data: { webContentId: webContent.id, options: autoResize } }); }, setBackgroundColor: async (color) => { return await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'borrowView_setBackgroundColor', data: { webContentId: webContent.id, options: color } }); }, }; return virtualView; }; /** * 为窗口对象设置扩展属性 * * @param window - 窗口对象 * @param data - 窗口数据 */ const setWindowProperties = (window, data) => { if (!window) return; window._name = data.winName || ''; window._type = data.winType || ''; window._extraData = data.winExtraData || ''; window._initUrl = data.winInitUrl || ''; }; async function create(options) { const remote = getRemote(); // 通过 IPC 调用主进程创建窗口 const data = await eIpc.RendererIPC.invokeMain('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'create', data: options }); // 尝试通过 ID 获取窗口 let window = remote.BrowserWindow.fromId(data.winId); // 如果不是 BrowserWindow,尝试获取 BrowserView if (!window) { const webContent = remote.webContents.fromId(data.winId); if (webContent) { window = getBrowserViewByWebContent(webContent); } } // 设置窗口属性 if (window) { setWindowProperties(window, data); } return window; } /** * 获取当前窗口实例 * * @returns Promise<BWItem | BVItem | undefined> 当前窗口实例 */ const getCurrentWindow = async () => { const remote = getRemote(); const currentWindow = remote.getCurrentWindow(); return await get(currentWindow.id); }; /** * 根据 ID 或名称获取窗口 * * @param idOrName - 窗口 ID 或名称 * @returns Promise<BWItem | BVItem | undefined> 窗口实例 * * @example * ```typescript * // 通过 ID 获取 * const window = await get(1); * * // 通过名称获取 * const window = await get('main-window'); * ``` */ const get = async (idOrName) => { const remote = getRemote(); // 通过 IPC 获取窗口信息 const data = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'get', data: idOrName }); // 尝试通过 ID 获取窗口 let window = remote.BrowserWindow.fromId(data.winId); // 如果不是 BrowserWindow,尝试获取 BrowserView if (!window) { const webContent = remote.webContents.fromId(data.winId); if (webContent) { window = getBrowserViewByWebContent(webContent); } } // 设置窗口属性 if (window) { setWindowProperties(window, data); } return window; }; async function getAll(type) { const remote = getRemote(); // 通过 IPC 获取所有窗口信息 const windowData = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'getAll', data: undefined }); const allWindows = new Map(); const browserWindows = new Map(); const browserViews = new Map(); const allBrowserWindows = remote.BrowserWindow.getAllWindows(); // 遍历窗口数据 for (const key in windowData) { const element = windowData[key]; let window; // 尝试在 BrowserWindow 中查找 window = allBrowserWindows.find(win => `${win.id}` === `${element?.winId}`); // 如果不是 BrowserWindow,尝试获取 BrowserView if (!window) { const webContent = remote.webContents.fromId(element.winId); if (webContent) { window = getBrowserViewByWebContent(webContent); } } if (window) { // 设置窗口属性 try { window.id = Number(element?.winId); } catch (error) { // 忽略设置 ID 失败的情况 } setWindowProperties(window, { winId: element.winId, winName: element.winName, winType: element.winType, winExtraData: element.winExtraData, winInitUrl: element.winInitUrl }); const windowId = window.id || window._id; allWindows.set(windowId, window); // 按类型分类 if (window._type === 'BW') { browserWindows.set(windowId, window); } else if (window._type === 'BV') { browserViews.set(windowId, window); } } } // 根据类型返回对应的映射表 if (type === 'BW') { return browserWindows; } if (type === 'BV') { return browserViews; } return allWindows; } /** * 关闭指定窗口 * * @param idOrName - 窗口 ID 或名称 * @returns Promise<boolean> 操作是否成功 */ const close = async (idOrName) => { await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'close', data: idOrName }); return true; }; /** * 关闭所有窗口 * * @returns Promise<boolean> 操作是否成功 */ const closeAll = async () => { await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'closeAll', data: undefined }); return true; }; /** * 根据 WebContents ID 查找对应的窗口 * * @param webContentId - WebContents ID * @returns Promise<BWItem | BVItem | undefined> 窗口实例 */ const getWindowForWebContentId = async (webContentId) => { const winId = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'getWindowForWebContentId', data: webContentId }); return get(winId); }; /** * 重命名窗口 * * @param idOrName - 窗口 ID 或名称 * @param newName - 新名称 * @returns Promise<BWItem | BVItem | undefined> 重命名后的窗口实例 */ const rename = async (idOrName, newName) => { const data = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'rename', data: { idOrName, newName } }); return get(data.winId); }; /** * 重置窗口初始化URL * * @param idOrName - 窗口 ID 或名称 * @param url - 新URL * @returns Promise<BWItem | BVItem | undefined> 重置后的窗口实例 */ const reInitUrl = async (idOrName, url) => { const data = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'reInitUrl', data: { idOrName, url } }); return get(data.winId); }; /** * 设置预加载 WebContents 配置 * * @param preloadWebContentsConfig - 预加载配置 */ const setPreloadWebContentsConfig = async (preloadWebContentsConfig) => { await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'setPreloadWebContentsConfig', data: preloadWebContentsConfig }); }; /** * 获取预加载脚本路径 * 使用缓存机制避免重复请求 * * @returns Promise<string | undefined> 预加载脚本路径 */ const getPreload = (() => { let cachedPreload = undefined; return async () => { // 如果已有缓存,直接返回 if (cachedPreload !== undefined) { return cachedPreload; } // 通过 IPC 获取预加载脚本路径 const data = await getIpc().invoke('__ELECTRON_WINDOW_MANAGER_IPC_CHANNEL__', { type: 'getPreload', data: undefined, }); // 缓存结果 cachedPreload = data; return data; }; })(); // 初始化时获取预加载脚本 getPreload(); // ==================== 开发工具相关(已注释) ==================== /* * 开发工具相关功能(已注释,可根据需要启用) * * const handleOpenDevTools = (e: HTMLElement, ev: KeyboardEvent): any => { * const remote = getRemote() * const webContents = remote.getCurrentWebContents() * webContents.openDevTools({ * mode: 'detach' * }) * return ''; * } * * export const registerDevTools = () => { * document.body.removeEventListener('keydown', handleOpenDevTools) * document.body.addEventListener('keydown', handleOpenDevTools) * } */ exports.close = close; exports.closeAll = closeAll; exports.create = create; exports.get = get; exports.getAll = getAll; exports.getCurrentWindow = getCurrentWindow; exports.getPreload = getPreload; exports.getWindowForWebContentId = getWindowForWebContentId; exports.reInitUrl = reInitUrl; exports.rename = rename; exports.setPreloadWebContentsConfig = setPreloadWebContentsConfig; //# sourceMappingURL=index.js.map