综合更新
This commit is contained in:
1 parent
d733c21145
commit
147f3b7045
57 files changed
+3038
-800
No files matched your search
@@ -1,14 +1,32 @@
|
||||
import { BrowserWindow, ipcMain, screen } from "electron";
|
||||
import { BrowserWindow, desktopCapturer, ipcMain, screen } from "electron";
|
||||
import { join } from "path";
|
||||
import { ConfigService } from "../services/config";
|
||||
|
||||
// Linux D-Bus通知服务
|
||||
// 通知交付分流:Windows 走特制液态玻璃通知窗口;
|
||||
// Linux / macOS 走系统通知中心(systemNotificationService)
|
||||
const isLinux = process.platform === "linux";
|
||||
let linuxNotificationService:
|
||||
| typeof import("../services/linuxNotificationService")
|
||||
const isMac = process.platform === "darwin";
|
||||
const usesSystemNotifications = isLinux || isMac;
|
||||
|
||||
// 原生液态玻璃(Windows 专用):DXGI 零拷贝采集 + D3D11 玻璃管线 + DComp 直接上屏,
|
||||
// 感知滞后中位 ~6ms(Chromium 流方案 ~77ms),渲染完全不经过 Electron 进程。
|
||||
// 不可用(系统过旧 / 二进制缺失)时自动回退 Chromium 桌面流管线
|
||||
type NativeGlassModule = typeof import("@hicccc77/electron-liquid-glass");
|
||||
let nativeGlass: NativeGlassModule | null = null;
|
||||
if (!usesSystemNotifications) {
|
||||
try {
|
||||
const mod: NativeGlassModule = require("@hicccc77/electron-liquid-glass");
|
||||
nativeGlass = mod.isSupported() ? mod : null;
|
||||
} catch {
|
||||
nativeGlass = null;
|
||||
}
|
||||
}
|
||||
|
||||
let systemNotificationService:
|
||||
| typeof import("../services/systemNotificationService")
|
||||
| null = null;
|
||||
|
||||
// 用于处理通知点击的回调函数(在Linux上用于导航到会话)
|
||||
// 用于处理通知点击的回调函数(系统通知平台用于导航到会话)
|
||||
let onNotificationNavigate: ((payload: unknown) => void) | null = null;
|
||||
|
||||
export function setNotificationNavigateHandler(
|
||||
@@ -20,19 +38,119 @@ export function setNotificationNavigateHandler(
|
||||
let notificationWindow: BrowserWindow | null = null;
|
||||
let closeTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
// 原生玻璃面板:与通知窗口一样常驻复用(创建后隐藏待命),
|
||||
// 展示期跟随渲染层上报的卡片实测矩形(notification:glassRect)
|
||||
let glassPanel: import("@hicccc77/electron-liquid-glass").GlassPanel | null = null;
|
||||
// 面板创建时的 dpr:原生端几何常量按 dpr 换算且仅在创建时设定,
|
||||
// 显示器缩放变化后必须重建面板,否则复用旧 dpr 会算错折射几何
|
||||
let glassPanelDpr = 0;
|
||||
|
||||
function destroyGlassPanel() {
|
||||
if (glassPanel) {
|
||||
try {
|
||||
glassPanel.destroy();
|
||||
} catch {
|
||||
/* 面板已随会话销毁 */
|
||||
}
|
||||
glassPanel = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 视觉参数换算(缺省值与渲染层 GLASS_PARAMS 一致,CSS 值 → 物理像素/比例)
|
||||
function toGlassParams(payload: Record<string, number | undefined>, scale: number) {
|
||||
return {
|
||||
cornerRadius: (payload.cornerRadius ?? 16) * scale,
|
||||
blurSigma: (payload.blurSigma ?? 2) * scale,
|
||||
displacementScale: payload.displacementScale ?? 70,
|
||||
aberrationIntensity: payload.aberrationIntensity ?? 1,
|
||||
saturation: (payload.saturation ?? 140) / 100,
|
||||
};
|
||||
}
|
||||
|
||||
// 创建或复用玻璃面板并同步全部状态(不负责 show/hide)。
|
||||
// 空闲预热与 glassRect 上报共用此路径:预热时占位几何 + 空亮度带,
|
||||
// 首次上报会以实测值幂等覆盖
|
||||
function ensureGlassPanel(
|
||||
bounds: { x: number; y: number; width: number; height: number },
|
||||
params: ReturnType<typeof toGlassParams>,
|
||||
scale: number,
|
||||
bands: Array<{ id: number; x: number; y: number; width: number; height: number }>,
|
||||
) {
|
||||
if (!nativeGlass || !notificationWindow || notificationWindow.isDestroyed()) {
|
||||
return null;
|
||||
}
|
||||
if (glassPanel && glassPanelDpr !== scale) destroyGlassPanel();
|
||||
if (!glassPanel) {
|
||||
glassPanel = nativeGlass.createPanel({
|
||||
...bounds,
|
||||
...params,
|
||||
dpr: scale,
|
||||
anchorWindow: notificationWindow,
|
||||
lumaBands: bands,
|
||||
onLuma: (bandStats) => {
|
||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||
notificationWindow.webContents.send("notification:luma", bandStats);
|
||||
}
|
||||
},
|
||||
});
|
||||
glassPanelDpr = scale;
|
||||
} else {
|
||||
glassPanel.setBounds(bounds);
|
||||
glassPanel.setParams(params);
|
||||
glassPanel.setLumaBands(bands);
|
||||
glassPanel.anchor(notificationWindow);
|
||||
}
|
||||
return glassPanel;
|
||||
}
|
||||
|
||||
// —— Chromium 桌面流回退管线(仅原生玻璃不可用时启用:系统过旧 / 二进制缺失)——
|
||||
// 透明窗口内 backdrop-filter 采样不到桌面像素(Chromium 只能采样页面内容),
|
||||
// 回退路径下折射源由渲染层用 getUserMedia 开启的实时桌面视频流提供。
|
||||
// 主进程只负责预热并缓存屏幕采集源 ID:desktopCapturer.getSources 会同步
|
||||
// 初始化 DirectX 采集管线(实测阻塞主线程 300ms+,不要缩略图也一样),
|
||||
// 绝不能出现在通知弹出路径上,否则每条通知都会让整个应用卡一下。
|
||||
// 通知窗口自身已设置 content protection,不会被拍进视频流造成折射回环
|
||||
let cachedSourceId: string | null = null;
|
||||
let sourceIdRefreshing: Promise<void> | null = null;
|
||||
|
||||
function refreshDesktopSourceId(): Promise<void> {
|
||||
if (sourceIdRefreshing) return sourceIdRefreshing;
|
||||
sourceIdRefreshing = (async () => {
|
||||
try {
|
||||
const display = screen.getPrimaryDisplay();
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ["screen"],
|
||||
thumbnailSize: { width: 0, height: 0 },
|
||||
});
|
||||
const source =
|
||||
sources.find((s) => s.display_id === String(display.id)) ?? sources[0];
|
||||
cachedSourceId = source?.id ?? null;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[NotificationWindow] Failed to refresh desktop source id:",
|
||||
error,
|
||||
);
|
||||
} finally {
|
||||
sourceIdRefreshing = null;
|
||||
}
|
||||
})();
|
||||
return sourceIdRefreshing;
|
||||
}
|
||||
|
||||
export function destroyNotificationWindow() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = null;
|
||||
}
|
||||
lastNotificationData = null;
|
||||
destroyGlassPanel();
|
||||
|
||||
// Linux:关闭通知服务并清理缓存(fire-and-forget,不阻塞退出)
|
||||
if (isLinux && linuxNotificationService) {
|
||||
linuxNotificationService.shutdownLinuxNotificationService().catch((error) => {
|
||||
console.warn("[NotificationWindow] Failed to shutdown Linux notification service:", error);
|
||||
// Linux/macOS:关闭系统通知服务并清理缓存(fire-and-forget,不阻塞退出)
|
||||
if (usesSystemNotifications && systemNotificationService) {
|
||||
systemNotificationService.shutdownSystemNotificationService().catch((error) => {
|
||||
console.warn("[NotificationWindow] Failed to shutdown system notification service:", error);
|
||||
});
|
||||
linuxNotificationService = null;
|
||||
systemNotificationService = null;
|
||||
}
|
||||
|
||||
if (!notificationWindow || notificationWindow.isDestroyed()) {
|
||||
@@ -50,6 +168,17 @@ export function destroyNotificationWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
// 窗口通过 min/max 锁定尺寸,程序化调整尺寸前需要同步放宽限制。
|
||||
// 尺寸未变化时直接跳过:可见状态下重复 setSize 会让 DWM
|
||||
// 短暂拉伸旧帧缓冲,在通知周围闪出一圈"幽灵轮廓"
|
||||
function applyWindowSize(win: BrowserWindow, width: number, height: number) {
|
||||
const [currentWidth, currentHeight] = win.getSize();
|
||||
if (currentWidth === width && currentHeight === height) return;
|
||||
win.setMinimumSize(width, height);
|
||||
win.setMaximumSize(width, height);
|
||||
win.setSize(width, height);
|
||||
}
|
||||
|
||||
export function createNotificationWindow() {
|
||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||
return notificationWindow;
|
||||
@@ -68,9 +197,12 @@ export function createNotificationWindow() {
|
||||
notificationWindow = new BrowserWindow({
|
||||
width: width,
|
||||
height: height,
|
||||
type: "toolbar", // 有助于在某些操作系统上保持置顶
|
||||
type: "toolbar", // 辅助置顶(仅 Windows 走此窗口)
|
||||
frame: false,
|
||||
// 无边框透明窗口:不会出现 DWM 材质窗口的系统描边,
|
||||
// 玻璃底下的"桌面"由原生面板或渲染层的实时桌面视频流提供
|
||||
transparent: true,
|
||||
hasShadow: false, // 卡片投影由 CSS 提供
|
||||
resizable: false,
|
||||
show: false,
|
||||
alwaysOnTop: true,
|
||||
@@ -85,6 +217,13 @@ export function createNotificationWindow() {
|
||||
},
|
||||
});
|
||||
|
||||
// 把窗口从屏幕采集中排除(WDA_EXCLUDEFROMCAPTURE,Win10 2004+):
|
||||
// 玻璃靠采集屏幕获得折射像素,不排除自身会拍到自己形成无限回环。
|
||||
// 副作用:系统截图/录屏中看不到通知窗口本体
|
||||
notificationWindow.setContentProtection(true);
|
||||
|
||||
applyWindowSize(notificationWindow, width, height);
|
||||
|
||||
// notificationWindow.webContents.openDevTools({ mode: 'detach' }) // DEBUG: Force Open DevTools
|
||||
notificationWindow.setIgnoreMouseEvents(true, { forward: true }); // 初始点击穿透
|
||||
|
||||
@@ -99,6 +238,12 @@ export function createNotificationWindow() {
|
||||
console.log("[NotificationWindow] Loading URL:", loadUrl);
|
||||
notificationWindow.loadURL(loadUrl);
|
||||
|
||||
// Chromium 会按 file:// 域持久化页面缩放(主窗口与通知窗口同域):
|
||||
// 任何来源的缩放残留都会让通知按错误的逻辑尺寸排版,这里强制钉回 1
|
||||
notificationWindow.webContents.on("did-finish-load", () => {
|
||||
notificationWindow?.webContents.setZoomFactor(1);
|
||||
});
|
||||
|
||||
notificationWindow.on("closed", () => {
|
||||
notificationWindow = null;
|
||||
});
|
||||
@@ -139,9 +284,9 @@ export async function showNotification(data: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Linux 使用 D-Bus 通知
|
||||
if (isLinux) {
|
||||
await showLinuxNotification(data);
|
||||
// Linux / macOS 走系统通知中心,仅 Windows 使用特制液态玻璃窗口
|
||||
if (usesSystemNotifications) {
|
||||
await showViaSystemNotification(data);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,24 +307,22 @@ export async function showNotification(data: any) {
|
||||
}
|
||||
}
|
||||
|
||||
// 显示Linux通知
|
||||
async function showLinuxNotification(data: any) {
|
||||
if (!linuxNotificationService) {
|
||||
// 经系统通知中心显示(Linux / macOS)
|
||||
async function showViaSystemNotification(data: any) {
|
||||
if (!systemNotificationService) {
|
||||
try {
|
||||
linuxNotificationService =
|
||||
await import("../services/linuxNotificationService");
|
||||
systemNotificationService =
|
||||
await import("../services/systemNotificationService");
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[NotificationWindow] Failed to load Linux notification service:",
|
||||
"[NotificationWindow] Failed to load system notification service:",
|
||||
error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { showLinuxNotification: showNotification } = linuxNotificationService;
|
||||
|
||||
const notificationData = {
|
||||
systemNotificationService.showSystemNotification({
|
||||
title: data.title,
|
||||
content: data.content,
|
||||
avatarUrl: data.avatarUrl,
|
||||
@@ -188,21 +331,20 @@ async function showLinuxNotification(data: any) {
|
||||
insightRecordId: data.insightRecordId,
|
||||
targetRoute: data.targetRoute,
|
||||
expireTimeout: 5000,
|
||||
};
|
||||
|
||||
showNotification(notificationData);
|
||||
});
|
||||
}
|
||||
|
||||
let lastNotificationData: any = null;
|
||||
|
||||
async function showAndSend(win: BrowserWindow, data: any) {
|
||||
lastNotificationData = data;
|
||||
const config = ConfigService.getInstance();
|
||||
const position = (await config.get("notificationPosition")) || "top-right";
|
||||
|
||||
// 更新位置
|
||||
const { width: screenWidth, height: screenHeight } =
|
||||
screen.getPrimaryDisplay().workAreaSize;
|
||||
// 更新位置:基于工作区完整矩形(含原点偏移)定位。
|
||||
// macOS 菜单栏、Windows 任务栏靠上/靠左时工作区原点不为 (0,0),
|
||||
// 只用 workAreaSize 会把通知压进系统栏下面
|
||||
const display = screen.getPrimaryDisplay();
|
||||
const workArea = display.workArea;
|
||||
const winWidth = position === "top-center" ? 280 : 344;
|
||||
const winHeight = 114;
|
||||
const padding = 20;
|
||||
@@ -212,57 +354,81 @@ async function showAndSend(win: BrowserWindow, data: any) {
|
||||
|
||||
switch (position) {
|
||||
case "top-center":
|
||||
x = (screenWidth - winWidth) / 2;
|
||||
y = padding;
|
||||
x = workArea.x + (workArea.width - winWidth) / 2;
|
||||
y = workArea.y + padding;
|
||||
break;
|
||||
case "top-right":
|
||||
x = screenWidth - winWidth - padding;
|
||||
y = padding;
|
||||
x = workArea.x + workArea.width - winWidth - padding;
|
||||
y = workArea.y + padding;
|
||||
break;
|
||||
case "bottom-right":
|
||||
x = screenWidth - winWidth - padding;
|
||||
y = screenHeight - winHeight - padding;
|
||||
x = workArea.x + workArea.width - winWidth - padding;
|
||||
y = workArea.y + workArea.height - winHeight - padding;
|
||||
break;
|
||||
case "top-left":
|
||||
x = padding;
|
||||
y = padding;
|
||||
x = workArea.x + padding;
|
||||
y = workArea.y + padding;
|
||||
break;
|
||||
case "bottom-left":
|
||||
x = padding;
|
||||
y = screenHeight - winHeight - padding;
|
||||
x = workArea.x + padding;
|
||||
y = workArea.y + workArea.height - winHeight - padding;
|
||||
break;
|
||||
}
|
||||
|
||||
win.setPosition(Math.floor(x), Math.floor(y));
|
||||
win.setSize(winWidth, winHeight); // 确保尺寸
|
||||
const winX = Math.floor(x);
|
||||
const winY = Math.floor(y);
|
||||
|
||||
// 采集源 ID 使用启动时预热的缓存;仅当首条通知早于预热完成时补一次。
|
||||
// 除此之外通知路径不触碰 desktopCapturer,弹出过程主线程零阻塞。
|
||||
// 原生玻璃可用时折射由原生面板提供,渲染层不再开启 Chromium 桌面流
|
||||
if (!nativeGlass && !cachedSourceId) await refreshDesktopSourceId();
|
||||
const payload = {
|
||||
...data,
|
||||
position,
|
||||
backdrop: {
|
||||
native: Boolean(nativeGlass),
|
||||
sourceId: nativeGlass ? null : cachedSourceId,
|
||||
winX,
|
||||
winY,
|
||||
width: display.size.width,
|
||||
height: display.size.height,
|
||||
},
|
||||
};
|
||||
lastNotificationData = payload;
|
||||
|
||||
win.setPosition(winX, winY);
|
||||
// 窗口高度始终沿用渲染层的实测校准值(notification:resize),
|
||||
// 这里只同步宽度;反复重置高度会造成 114→实测高度的弹跳闪烁
|
||||
const [, currentHeight] = win.getSize();
|
||||
applyWindowSize(win, winWidth, currentHeight);
|
||||
|
||||
win.webContents.send("notification:show", payload);
|
||||
|
||||
// 设为可交互
|
||||
win.setIgnoreMouseEvents(false);
|
||||
win.showInactive(); // 显示但不聚焦
|
||||
win.setAlwaysOnTop(true, "screen-saver"); // 最高层级
|
||||
|
||||
win.webContents.send("notification:show", { ...data, position });
|
||||
|
||||
// 自动关闭计时器通常由渲染进程管理
|
||||
// 渲染进程发送 'notification:close' 来隐藏窗口
|
||||
}
|
||||
|
||||
// 注册通知处理
|
||||
export async function registerNotificationHandlers() {
|
||||
// Linux: 初始化D-Bus服务
|
||||
if (isLinux) {
|
||||
// Linux / macOS:初始化系统通知服务
|
||||
if (usesSystemNotifications) {
|
||||
try {
|
||||
const linuxNotificationModule =
|
||||
await import("../services/linuxNotificationService");
|
||||
linuxNotificationService = linuxNotificationModule;
|
||||
const systemNotificationModule =
|
||||
await import("../services/systemNotificationService");
|
||||
systemNotificationService = systemNotificationModule;
|
||||
|
||||
// 初始化服务
|
||||
await linuxNotificationModule.initLinuxNotificationService();
|
||||
await systemNotificationModule.initSystemNotificationService();
|
||||
|
||||
// 在Linux上注册通知点击回调
|
||||
linuxNotificationModule.onNotificationAction((payload: unknown) => {
|
||||
// 注册通知点击回调(导航到会话/洞察)
|
||||
systemNotificationModule.onNotificationAction((payload: unknown) => {
|
||||
console.log(
|
||||
"[NotificationWindow] Linux notification clicked, sessionId:",
|
||||
"[NotificationWindow] System notification clicked, payload:",
|
||||
payload,
|
||||
);
|
||||
// 如果设置了导航处理程序,则使用该处理程序;否则,回退到ipcMain方法。
|
||||
@@ -278,11 +444,11 @@ export async function registerNotificationHandlers() {
|
||||
});
|
||||
|
||||
console.log(
|
||||
"[NotificationWindow] Linux notification service initialized",
|
||||
"[NotificationWindow] System notification service initialized",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[NotificationWindow] Failed to initialize Linux notification service:",
|
||||
"[NotificationWindow] Failed to initialize system notification service:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
@@ -293,20 +459,60 @@ export async function registerNotificationHandlers() {
|
||||
});
|
||||
|
||||
ipcMain.handle("notification:close", () => {
|
||||
if (isLinux && linuxNotificationService) {
|
||||
// 注册通知点击回调函数。Linux通知通过D-Bus自动关闭,但我们可以根据需要进行跟踪
|
||||
if (usesSystemNotifications) {
|
||||
// 系统通知由通知中心管理生命周期,无窗口可关
|
||||
return;
|
||||
}
|
||||
// 窗口即将隐藏,玻璃面板立即消失(渲染层通常已提前发过淡出信号)
|
||||
glassPanel?.hide(0);
|
||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||
notificationWindow.hide();
|
||||
notificationWindow.setIgnoreMouseEvents(true, { forward: true });
|
||||
}
|
||||
});
|
||||
|
||||
// —— 原生玻璃面板生命周期(仅 nativeGlass 可用时渲染层才会发这些消息)——
|
||||
|
||||
// 渲染层在卡片入场动画落定后上报实测几何(窗口本地 CSS 像素)
|
||||
ipcMain.on("notification:glassRect", (_event, payload) => {
|
||||
if (!nativeGlass || !notificationWindow || notificationWindow.isDestroyed()) return;
|
||||
const display = screen.getDisplayMatching(notificationWindow.getBounds());
|
||||
// 优先用渲染层实测的 devicePixelRatio(已含可能的页面缩放),显示器缩放兜底
|
||||
const scale = payload.dpr || display.scaleFactor;
|
||||
const [winX, winY] = notificationWindow.getPosition();
|
||||
const toPhysical = (rect: { x: number; y: number; width: number; height: number }) => ({
|
||||
x: Math.round(rect.x * scale),
|
||||
y: Math.round(rect.y * scale),
|
||||
width: Math.round(rect.width * scale),
|
||||
height: Math.round(rect.height * scale),
|
||||
});
|
||||
const bounds = {
|
||||
x: Math.round((winX + payload.card.x) * scale),
|
||||
y: Math.round((winY + payload.card.y) * scale),
|
||||
width: Math.round(payload.card.width * scale),
|
||||
height: Math.round(payload.card.height * scale),
|
||||
};
|
||||
// 亮度带矩形是卡片本地坐标(带 id:0=整卡 1=标题行 2=正文)
|
||||
const bands = (payload.bands ?? []).map(
|
||||
(band: { id: number; x: number; y: number; width: number; height: number }) => ({
|
||||
id: band.id,
|
||||
...toPhysical(band),
|
||||
}),
|
||||
);
|
||||
|
||||
const panel = ensureGlassPanel(bounds, toGlassParams(payload, scale), scale, bands);
|
||||
panel?.show(120);
|
||||
});
|
||||
|
||||
// 渲染层退场动画开始:面板与卡片的 0.3s 渐隐同步淡出
|
||||
ipcMain.on("notification:glassHide", () => {
|
||||
glassPanel?.hide(240);
|
||||
});
|
||||
|
||||
// Handle renderer ready event (fix race condition)
|
||||
ipcMain.on("notification:ready", (event) => {
|
||||
if (isLinux) {
|
||||
// Linux不需要通知窗口,拦截通知窗口渲染
|
||||
if (usesSystemNotifications) {
|
||||
// 系统通知平台不需要通知窗口,拦截通知窗口渲染
|
||||
return;
|
||||
}
|
||||
console.log("[NotificationWindow] Renderer ready, checking cached data");
|
||||
@@ -323,10 +529,44 @@ export async function registerNotificationHandlers() {
|
||||
}
|
||||
});
|
||||
|
||||
// 启动空闲期预热(系统通知平台不需要):
|
||||
// - 采集源 ID:getSources 初始化采集管线会同步阻塞主线程 300ms+,放到空闲期一次性完成
|
||||
// (原生玻璃可用时不需要 Chromium 采集源,跳过)
|
||||
// - 预创建通知窗口:首条通知免去窗口创建和渲染器冷启动
|
||||
// - 预创建原生玻璃面板(隐藏待命):worker 线程、D3D 设备、着色器编译、
|
||||
// DComp 窗口链全部离开首条通知的可见路径(原生端实测 ~150ms);
|
||||
// 隐藏面板零渲染零采集,常驻开销可忽略
|
||||
if (!usesSystemNotifications) {
|
||||
setTimeout(() => {
|
||||
if (!nativeGlass) refreshDesktopSourceId();
|
||||
const win = createNotificationWindow();
|
||||
if (nativeGlass && win) {
|
||||
const scale = screen.getPrimaryDisplay().scaleFactor;
|
||||
const [winX, winY] = win.getPosition();
|
||||
ensureGlassPanel(
|
||||
{
|
||||
x: Math.round(winX * scale),
|
||||
y: Math.round(winY * scale),
|
||||
width: Math.round(344 * scale),
|
||||
height: Math.round(96 * scale),
|
||||
},
|
||||
toGlassParams({}, scale),
|
||||
scale,
|
||||
[],
|
||||
);
|
||||
}
|
||||
}, 3000);
|
||||
if (!nativeGlass) {
|
||||
// 显示器增删后源 ID 可能失效,重新预热
|
||||
screen.on("display-added", () => refreshDesktopSourceId());
|
||||
screen.on("display-removed", () => refreshDesktopSourceId());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle resize request from renderer
|
||||
ipcMain.on("notification:resize", (event, { width, height }) => {
|
||||
if (isLinux) {
|
||||
// Linux 通知通过D-Bus自动调整大小
|
||||
if (usesSystemNotifications) {
|
||||
// 系统通知由通知中心排版,无需窗口尺寸同步
|
||||
return;
|
||||
}
|
||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||
@@ -342,11 +582,8 @@ export async function registerNotificationHandlers() {
|
||||
// We can re-call setPosition or just let it be.
|
||||
// If bottom-right, y needs to prevent overflow.
|
||||
|
||||
// Ideally we get current config position
|
||||
const bounds = notificationWindow.getBounds();
|
||||
// Check if we need to adjust Y?
|
||||
// For now, let's just set the size as requested.
|
||||
notificationWindow.setSize(Math.round(width), Math.round(height));
|
||||
applyWindowSize(notificationWindow, Math.round(width), Math.round(height));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user