@lynker-desktop/electron-window-manager
Version:
electron-window-manager
398 lines (395 loc) • 12.3 kB
JavaScript
import eIpc from '@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)
* }
*/
export { close, closeAll, create, get, getAll, getCurrentWindow, getPreload, getWindowForWebContentId, reInitUrl, rename, setPreloadWebContentsConfig };
//# sourceMappingURL=index.js.map