mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-07 07:25:52 +00:00
Compare commits
5 Commits
nightly-de
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef3d220521 | ||
|
|
e8aaae5616 | ||
|
|
45deb99e3d | ||
|
|
b821d370f9 | ||
|
|
60248b28f8 |
@@ -27,7 +27,7 @@ import { windowsHelloService } from './services/windowsHelloService'
|
|||||||
import { exportCardDiagnosticsService } from './services/exportCardDiagnosticsService'
|
import { exportCardDiagnosticsService } from './services/exportCardDiagnosticsService'
|
||||||
import { cloudControlService } from './services/cloudControlService'
|
import { cloudControlService } from './services/cloudControlService'
|
||||||
|
|
||||||
import { destroyNotificationWindow, registerNotificationHandlers, showNotification } from './windows/notificationWindow'
|
import { destroyNotificationWindow, registerNotificationHandlers, showNotification, setNotificationNavigateHandler } from './windows/notificationWindow'
|
||||||
import { httpService } from './services/httpService'
|
import { httpService } from './services/httpService'
|
||||||
import { messagePushService } from './services/messagePushService'
|
import { messagePushService } from './services/messagePushService'
|
||||||
import { bizService } from './services/bizService'
|
import { bizService } from './services/bizService'
|
||||||
@@ -740,6 +740,14 @@ function createWindow(options: { autoShow?: boolean } = {}) {
|
|||||||
win.webContents.send('navigate-to-session', sessionId)
|
win.webContents.send('navigate-to-session', sessionId)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 设置用于D-Bus通知的Linux通知导航处理程序
|
||||||
|
setNotificationNavigateHandler((sessionId: string) => {
|
||||||
|
if (win.isMinimized()) win.restore()
|
||||||
|
win.show()
|
||||||
|
win.focus()
|
||||||
|
win.webContents.send('navigate-to-session', sessionId)
|
||||||
|
})
|
||||||
|
|
||||||
// 拦截请求,修改 Referer 和 User-Agent 以通过微信 CDN 鉴权
|
// 拦截请求,修改 Referer 和 User-Agent 以通过微信 CDN 鉴权
|
||||||
session.defaultSession.webRequest.onBeforeSendHeaders(
|
session.defaultSession.webRequest.onBeforeSendHeaders(
|
||||||
{
|
{
|
||||||
|
|||||||
344
electron/services/linuxNotificationService.ts
Normal file
344
electron/services/linuxNotificationService.ts
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import dbus from "dbus-native";
|
||||||
|
import https from "https";
|
||||||
|
import http, { IncomingMessage } from "http";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
|
const BUS_NAME = "org.freedesktop.Notifications";
|
||||||
|
const OBJECT_PATH = "/org/freedesktop/Notifications";
|
||||||
|
|
||||||
|
export interface LinuxNotificationData {
|
||||||
|
sessionId?: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
expireTimeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationCallback = (sessionId: string) => void;
|
||||||
|
|
||||||
|
let sessionBus: dbus.DBusConnection | null = null;
|
||||||
|
let notificationCallbacks: NotificationCallback[] = [];
|
||||||
|
let pendingNotifications: Map<number, LinuxNotificationData> = new Map();
|
||||||
|
|
||||||
|
// 头像缓存:url->localFilePath
|
||||||
|
const avatarCache: Map<string, string> = new Map();
|
||||||
|
// 缓存目录
|
||||||
|
let avatarCacheDir: string | null = null;
|
||||||
|
|
||||||
|
async function getSessionBus(): Promise<dbus.DBusConnection> {
|
||||||
|
if (!sessionBus) {
|
||||||
|
sessionBus = dbus.sessionBus();
|
||||||
|
|
||||||
|
// 挂载底层socket的error事件,防止掉线即可
|
||||||
|
sessionBus.connection.on("error", (err: Error) => {
|
||||||
|
console.error("[LinuxNotification] D-Bus connection error:", err);
|
||||||
|
sessionBus = null; // 报错清理死对象
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sessionBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保缓存目录存在
|
||||||
|
async function ensureCacheDir(): Promise<string> {
|
||||||
|
if (!avatarCacheDir) {
|
||||||
|
avatarCacheDir = join(app.getPath("temp"), "weflow-avatars");
|
||||||
|
try {
|
||||||
|
await fs.mkdir(avatarCacheDir, { recursive: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"[LinuxNotification] Failed to create avatar cache dir:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return avatarCacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载头像到本地临时文件
|
||||||
|
async function downloadAvatarToLocal(url: string): Promise<string | null> {
|
||||||
|
// 检查缓存
|
||||||
|
if (avatarCache.has(url)) {
|
||||||
|
return avatarCache.get(url) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cacheDir = await ensureCacheDir();
|
||||||
|
// 生成唯一文件名
|
||||||
|
const fileName = `avatar_${Date.now()}_${Math.random().toString(36).substring(2, 8)}.png`;
|
||||||
|
const localPath = join(cacheDir, fileName);
|
||||||
|
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
// 微信 CDN 需要特殊的请求头才能下载图片
|
||||||
|
const options = {
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63090719) XWEB/8351",
|
||||||
|
Referer: "https://servicewechat.com/",
|
||||||
|
Accept:
|
||||||
|
"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
|
||||||
|
"Accept-Encoding": "gzip, deflate, br",
|
||||||
|
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||||
|
Connection: "keep-alive",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const callback = (res: IncomingMessage) => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
reject(new Error(`HTTP ${res.statusCode}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
res.on("data", (chunk: Buffer) => chunks.push(chunk));
|
||||||
|
res.on("end", async () => {
|
||||||
|
try {
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
await fs.writeFile(localPath, buffer);
|
||||||
|
avatarCache.set(url, localPath);
|
||||||
|
resolve();
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.on("error", reject);
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = url.startsWith("https")
|
||||||
|
? https.get(url, options, callback)
|
||||||
|
: http.get(url, options, callback);
|
||||||
|
|
||||||
|
req.on("error", reject);
|
||||||
|
req.setTimeout(10000, () => {
|
||||||
|
req.destroy();
|
||||||
|
reject(new Error("Download timeout"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[LinuxNotification] Avatar downloaded: ${url} -> ${localPath}`,
|
||||||
|
);
|
||||||
|
return localPath;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[LinuxNotification] Failed to download avatar:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function showLinuxNotification(
|
||||||
|
data: LinuxNotificationData,
|
||||||
|
): Promise<number | null> {
|
||||||
|
try {
|
||||||
|
const bus = await getSessionBus();
|
||||||
|
|
||||||
|
const appName = "WeFlow";
|
||||||
|
const replaceId = 0;
|
||||||
|
const expireTimeout = data.expireTimeout ?? 5000;
|
||||||
|
|
||||||
|
// 处理头像:下载到本地或使用URL
|
||||||
|
let appIcon = "";
|
||||||
|
let hints: any[] = [];
|
||||||
|
if (data.avatarUrl) {
|
||||||
|
// 优先尝试下载到本地
|
||||||
|
const localPath = await downloadAvatarToLocal(data.avatarUrl);
|
||||||
|
if (localPath) {
|
||||||
|
hints = [["image-path", ["s", localPath]]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
bus.invoke(
|
||||||
|
{
|
||||||
|
destination: BUS_NAME,
|
||||||
|
path: OBJECT_PATH,
|
||||||
|
interface: "org.freedesktop.Notifications",
|
||||||
|
member: "Notify",
|
||||||
|
signature: "susssasa{sv}i",
|
||||||
|
body: [
|
||||||
|
appName,
|
||||||
|
replaceId,
|
||||||
|
appIcon,
|
||||||
|
data.title,
|
||||||
|
data.content,
|
||||||
|
["default", "打开"], // 提供default action,否则系统不会抛出点击事件
|
||||||
|
hints,
|
||||||
|
// [], // 传空数组以避开a{sv}变体的序列化崩溃,有pendingNotifications映射维护保证不出错
|
||||||
|
expireTimeout,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
(err: Error | null, result: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("[LinuxNotification] Notify error:", err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const notificationId =
|
||||||
|
typeof result === "number" ? result : result[0];
|
||||||
|
if (data.sessionId) {
|
||||||
|
// 依赖Map实现点击追踪,没有使用D-Bus hints
|
||||||
|
pendingNotifications.set(notificationId, data);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`[LinuxNotification] Shown notification ${notificationId}: ${data.title}, icon: ${appIcon || "none"}`,
|
||||||
|
);
|
||||||
|
resolve(notificationId);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[LinuxNotification] Failed to show notification:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function closeLinuxNotification(
|
||||||
|
notificationId: number,
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const bus = await getSessionBus();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
bus.invoke(
|
||||||
|
{
|
||||||
|
destination: BUS_NAME,
|
||||||
|
path: OBJECT_PATH,
|
||||||
|
interface: "org.freedesktop.Notifications",
|
||||||
|
member: "CloseNotification",
|
||||||
|
signature: "u",
|
||||||
|
body: [notificationId],
|
||||||
|
},
|
||||||
|
(err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("[LinuxNotification] CloseNotification error:", err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingNotifications.delete(notificationId);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[LinuxNotification] Failed to close notification:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCapabilities(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const bus = await getSessionBus();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
bus.invoke(
|
||||||
|
{
|
||||||
|
destination: BUS_NAME,
|
||||||
|
path: OBJECT_PATH,
|
||||||
|
interface: "org.freedesktop.Notifications",
|
||||||
|
member: "GetCapabilities",
|
||||||
|
},
|
||||||
|
(err: Error | null, result: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("[LinuxNotification] GetCapabilities error:", err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(result as string[]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[LinuxNotification] Failed to get capabilities:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onNotificationAction(callback: NotificationCallback): void {
|
||||||
|
notificationCallbacks.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeNotificationCallback(
|
||||||
|
callback: NotificationCallback,
|
||||||
|
): void {
|
||||||
|
const index = notificationCallbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
notificationCallbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerNotificationCallback(sessionId: string): void {
|
||||||
|
for (const callback of notificationCallbacks) {
|
||||||
|
try {
|
||||||
|
callback(sessionId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[LinuxNotification] Callback error:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initLinuxNotificationService(): Promise<void> {
|
||||||
|
if (process.platform !== "linux") {
|
||||||
|
console.log("[LinuxNotification] Not on Linux, skipping init");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bus = await getSessionBus();
|
||||||
|
|
||||||
|
// 监听底层connection的message事件
|
||||||
|
bus.connection.on("message", (msg: any) => {
|
||||||
|
// type 4表示SIGNAL
|
||||||
|
if (
|
||||||
|
msg.type === 4 &&
|
||||||
|
msg.path === OBJECT_PATH &&
|
||||||
|
msg.interface === "org.freedesktop.Notifications"
|
||||||
|
) {
|
||||||
|
if (msg.member === "ActionInvoked") {
|
||||||
|
const [notificationId, actionId] = msg.body;
|
||||||
|
console.log(
|
||||||
|
`[LinuxNotification] Action invoked: ${notificationId}, ${actionId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果用户点击了通知本体,actionId会是'default'
|
||||||
|
if (actionId === "default") {
|
||||||
|
const data = pendingNotifications.get(notificationId);
|
||||||
|
if (data?.sessionId) {
|
||||||
|
triggerNotificationCallback(data.sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.member === "NotificationClosed") {
|
||||||
|
const [notificationId] = msg.body;
|
||||||
|
pendingNotifications.delete(notificationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// AddMatch用来接收信号
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
bus.invoke(
|
||||||
|
{
|
||||||
|
destination: "org.freedesktop.DBus",
|
||||||
|
path: "/org/freedesktop/DBus",
|
||||||
|
interface: "org.freedesktop.DBus",
|
||||||
|
member: "AddMatch",
|
||||||
|
signature: "s",
|
||||||
|
body: ["type='signal',interface='org.freedesktop.Notifications'"],
|
||||||
|
},
|
||||||
|
(err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("[LinuxNotification] AddMatch error:", err);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[LinuxNotification] Service initialized");
|
||||||
|
|
||||||
|
// 打印相关日志
|
||||||
|
const caps = await getCapabilities();
|
||||||
|
console.log("[LinuxNotification] Server capabilities:", caps);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[LinuxNotification] Failed to initialize:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
electron/types/dbus.d.ts
vendored
Normal file
18
electron/types/dbus.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
declare module 'dbus-native' {
|
||||||
|
namespace dbus {
|
||||||
|
interface DBusConnection {
|
||||||
|
invoke(options: any, callback: (err: Error | null, result?: any) => void): void;
|
||||||
|
on(event: string, listener: Function): void;
|
||||||
|
// 底层connection,用于监听signal
|
||||||
|
connection: {
|
||||||
|
on(event: string, listener: Function): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明sessionBus方法
|
||||||
|
function sessionBus(): DBusConnection;
|
||||||
|
function systemBus(): DBusConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export = dbus;
|
||||||
|
}
|
||||||
@@ -1,224 +1,333 @@
|
|||||||
import { BrowserWindow, ipcMain, screen } from 'electron'
|
import { BrowserWindow, ipcMain, screen } from "electron";
|
||||||
import { join } from 'path'
|
import { join } from "path";
|
||||||
import { ConfigService } from '../services/config'
|
import { ConfigService } from "../services/config";
|
||||||
|
|
||||||
let notificationWindow: BrowserWindow | null = null
|
// Linux D-Bus通知服务
|
||||||
let closeTimer: NodeJS.Timeout | null = null
|
const isLinux = process.platform === "linux";
|
||||||
|
let linuxNotificationService:
|
||||||
|
| typeof import("../services/linuxNotificationService")
|
||||||
|
| null = null;
|
||||||
|
|
||||||
|
// 用于处理通知点击的回调函数(在Linux上用于导航到会话)
|
||||||
|
let onNotificationNavigate: ((sessionId: string) => void) | null = null;
|
||||||
|
|
||||||
|
export function setNotificationNavigateHandler(
|
||||||
|
callback: (sessionId: string) => void,
|
||||||
|
) {
|
||||||
|
onNotificationNavigate = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
let notificationWindow: BrowserWindow | null = null;
|
||||||
|
let closeTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
export function destroyNotificationWindow() {
|
export function destroyNotificationWindow() {
|
||||||
if (closeTimer) {
|
if (closeTimer) {
|
||||||
clearTimeout(closeTimer)
|
clearTimeout(closeTimer);
|
||||||
closeTimer = null
|
closeTimer = null;
|
||||||
}
|
}
|
||||||
lastNotificationData = null
|
lastNotificationData = null;
|
||||||
|
|
||||||
if (!notificationWindow || notificationWindow.isDestroyed()) {
|
if (!notificationWindow || notificationWindow.isDestroyed()) {
|
||||||
notificationWindow = null
|
notificationWindow = null;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const win = notificationWindow
|
const win = notificationWindow;
|
||||||
notificationWindow = null
|
notificationWindow = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
win.destroy()
|
win.destroy();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[NotificationWindow] Failed to destroy window:', error)
|
console.warn("[NotificationWindow] Failed to destroy window:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createNotificationWindow() {
|
export function createNotificationWindow() {
|
||||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||||
return notificationWindow
|
return notificationWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDev = !!process.env.VITE_DEV_SERVER_URL
|
const isDev = !!process.env.VITE_DEV_SERVER_URL;
|
||||||
const iconPath = isDev
|
const iconPath = isDev
|
||||||
? join(__dirname, '../../public/icon.ico')
|
? join(__dirname, "../../public/icon.ico")
|
||||||
: join(process.resourcesPath, 'icon.ico')
|
: join(process.resourcesPath, "icon.ico");
|
||||||
|
|
||||||
console.log('[NotificationWindow] Creating window...')
|
console.log("[NotificationWindow] Creating window...");
|
||||||
const width = 344
|
const width = 344;
|
||||||
const height = 114
|
const height = 114;
|
||||||
|
|
||||||
// Update default creation size
|
// Update default creation size
|
||||||
notificationWindow = new BrowserWindow({
|
notificationWindow = new BrowserWindow({
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
type: 'toolbar', // 有助于在某些操作系统上保持置顶
|
type: "toolbar", // 有助于在某些操作系统上保持置顶
|
||||||
frame: false,
|
frame: false,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
show: false,
|
show: false,
|
||||||
alwaysOnTop: true,
|
alwaysOnTop: true,
|
||||||
skipTaskbar: true,
|
skipTaskbar: true,
|
||||||
focusable: false, // 不抢占焦点
|
focusable: false, // 不抢占焦点
|
||||||
icon: iconPath,
|
icon: iconPath,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: join(__dirname, 'preload.js'), // FIX: Use correct relative path (same dir in dist)
|
preload: join(__dirname, "preload.js"), // FIX: Use correct relative path (same dir in dist)
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
// devTools: true // Enable DevTools
|
// devTools: true // Enable DevTools
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
// notificationWindow.webContents.openDevTools({ mode: 'detach' }) // DEBUG: Force Open DevTools
|
// notificationWindow.webContents.openDevTools({ mode: 'detach' }) // DEBUG: Force Open DevTools
|
||||||
notificationWindow.setIgnoreMouseEvents(true, { forward: true }) // 初始点击穿透
|
notificationWindow.setIgnoreMouseEvents(true, { forward: true }); // 初始点击穿透
|
||||||
|
|
||||||
// 处理鼠标事件 (如果需要从渲染进程转发,但目前特定区域处理?)
|
// 处理鼠标事件 (如果需要从渲染进程转发,但目前特定区域处理?)
|
||||||
// 实际上,我们希望窗口可点击。
|
// 实际上,我们希望窗口可点击。
|
||||||
// 我们将在显示时将忽略鼠标事件设为 false。
|
// 我们将在显示时将忽略鼠标事件设为 false。
|
||||||
|
|
||||||
const loadUrl = isDev
|
const loadUrl = isDev
|
||||||
? `${process.env.VITE_DEV_SERVER_URL}#/notification-window`
|
? `${process.env.VITE_DEV_SERVER_URL}#/notification-window`
|
||||||
: `file://${join(__dirname, '../dist/index.html')}#/notification-window`
|
: `file://${join(__dirname, "../dist/index.html")}#/notification-window`;
|
||||||
|
|
||||||
console.log('[NotificationWindow] Loading URL:', loadUrl)
|
console.log("[NotificationWindow] Loading URL:", loadUrl);
|
||||||
notificationWindow.loadURL(loadUrl)
|
notificationWindow.loadURL(loadUrl);
|
||||||
|
|
||||||
notificationWindow.on('closed', () => {
|
notificationWindow.on("closed", () => {
|
||||||
notificationWindow = null
|
notificationWindow = null;
|
||||||
})
|
});
|
||||||
|
|
||||||
return notificationWindow
|
return notificationWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showNotification(data: any) {
|
export async function showNotification(data: any) {
|
||||||
// 先检查配置
|
// 先检查配置
|
||||||
const config = ConfigService.getInstance()
|
const config = ConfigService.getInstance();
|
||||||
const enabled = await config.get('notificationEnabled')
|
const enabled = await config.get("notificationEnabled");
|
||||||
if (enabled === false) return // 默认为 true
|
if (enabled === false) return; // 默认为 true
|
||||||
|
|
||||||
// 检查会话过滤
|
// 检查会话过滤
|
||||||
const filterMode = config.get('notificationFilterMode') || 'all'
|
const filterMode = config.get("notificationFilterMode") || "all";
|
||||||
const filterList = config.get('notificationFilterList') || []
|
const filterList = config.get("notificationFilterList") || [];
|
||||||
const sessionId = data.sessionId
|
const sessionId = data.sessionId;
|
||||||
|
|
||||||
if (sessionId && filterMode !== 'all' && filterList.length > 0) {
|
if (sessionId && filterMode !== "all" && filterList.length > 0) {
|
||||||
const isInList = filterList.includes(sessionId)
|
const isInList = filterList.includes(sessionId);
|
||||||
if (filterMode === 'whitelist' && !isInList) {
|
if (filterMode === "whitelist" && !isInList) {
|
||||||
// 白名单模式:不在列表中则不显示
|
// 白名单模式:不在列表中则不显示
|
||||||
return
|
return;
|
||||||
}
|
|
||||||
if (filterMode === 'blacklist' && isInList) {
|
|
||||||
// 黑名单模式:在列表中则不显示
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (filterMode === "blacklist" && isInList) {
|
||||||
let win = notificationWindow
|
// 黑名单模式:在列表中则不显示
|
||||||
if (!win || win.isDestroyed()) {
|
return;
|
||||||
win = createNotificationWindow()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!win) return
|
// Linux 使用 D-Bus 通知
|
||||||
|
if (isLinux) {
|
||||||
|
await showLinuxNotification(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 确保加载完成
|
let win = notificationWindow;
|
||||||
if (win.webContents.isLoading()) {
|
if (!win || win.isDestroyed()) {
|
||||||
win.once('ready-to-show', () => {
|
win = createNotificationWindow();
|
||||||
showAndSend(win!, data)
|
}
|
||||||
})
|
|
||||||
} else {
|
if (!win) return;
|
||||||
showAndSend(win, data)
|
|
||||||
}
|
// 确保加载完成
|
||||||
|
if (win.webContents.isLoading()) {
|
||||||
|
win.once("ready-to-show", () => {
|
||||||
|
showAndSend(win!, data);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showAndSend(win, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastNotificationData: any = null
|
// 显示Linux通知
|
||||||
|
async function showLinuxNotification(data: any) {
|
||||||
|
if (!linuxNotificationService) {
|
||||||
|
try {
|
||||||
|
linuxNotificationService =
|
||||||
|
await import("../services/linuxNotificationService");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"[NotificationWindow] Failed to load Linux notification service:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { showLinuxNotification: showNotification } = linuxNotificationService;
|
||||||
|
|
||||||
|
const notificationData = {
|
||||||
|
title: data.title,
|
||||||
|
content: data.content,
|
||||||
|
avatarUrl: data.avatarUrl,
|
||||||
|
sessionId: data.sessionId,
|
||||||
|
expireTimeout: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
showNotification(notificationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastNotificationData: any = null;
|
||||||
|
|
||||||
async function showAndSend(win: BrowserWindow, data: any) {
|
async function showAndSend(win: BrowserWindow, data: any) {
|
||||||
lastNotificationData = data
|
lastNotificationData = data;
|
||||||
const config = ConfigService.getInstance()
|
const config = ConfigService.getInstance();
|
||||||
const position = (await config.get('notificationPosition')) || 'top-right'
|
const position = (await config.get("notificationPosition")) || "top-right";
|
||||||
|
|
||||||
// 更新位置
|
// 更新位置
|
||||||
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize
|
const { width: screenWidth, height: screenHeight } =
|
||||||
const winWidth = position === 'top-center' ? 280 : 344
|
screen.getPrimaryDisplay().workAreaSize;
|
||||||
const winHeight = 114
|
const winWidth = position === "top-center" ? 280 : 344;
|
||||||
const padding = 20
|
const winHeight = 114;
|
||||||
|
const padding = 20;
|
||||||
|
|
||||||
let x = 0
|
let x = 0;
|
||||||
let y = 0
|
let y = 0;
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 'top-center':
|
case "top-center":
|
||||||
x = (screenWidth - winWidth) / 2
|
x = (screenWidth - winWidth) / 2;
|
||||||
y = padding
|
y = padding;
|
||||||
break
|
break;
|
||||||
case 'top-right':
|
case "top-right":
|
||||||
x = screenWidth - winWidth - padding
|
x = screenWidth - winWidth - padding;
|
||||||
y = padding
|
y = padding;
|
||||||
break
|
break;
|
||||||
case 'bottom-right':
|
case "bottom-right":
|
||||||
x = screenWidth - winWidth - padding
|
x = screenWidth - winWidth - padding;
|
||||||
y = screenHeight - winHeight - padding
|
y = screenHeight - winHeight - padding;
|
||||||
break
|
break;
|
||||||
case 'top-left':
|
case "top-left":
|
||||||
x = padding
|
x = padding;
|
||||||
y = padding
|
y = padding;
|
||||||
break
|
break;
|
||||||
case 'bottom-left':
|
case "bottom-left":
|
||||||
x = padding
|
x = padding;
|
||||||
y = screenHeight - winHeight - padding
|
y = screenHeight - winHeight - padding;
|
||||||
break
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
win.setPosition(Math.floor(x), Math.floor(y));
|
||||||
|
win.setSize(winWidth, winHeight); // 确保尺寸
|
||||||
|
|
||||||
|
// 设为可交互
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
const linuxNotificationModule =
|
||||||
|
await import("../services/linuxNotificationService");
|
||||||
|
linuxNotificationService = linuxNotificationModule;
|
||||||
|
|
||||||
|
// 初始化服务
|
||||||
|
await linuxNotificationModule.initLinuxNotificationService();
|
||||||
|
|
||||||
|
// 在Linux上注册通知点击回调
|
||||||
|
linuxNotificationModule.onNotificationAction((sessionId: string) => {
|
||||||
|
console.log(
|
||||||
|
"[NotificationWindow] Linux notification clicked, sessionId:",
|
||||||
|
sessionId,
|
||||||
|
);
|
||||||
|
// 如果设置了导航处理程序,则使用该处理程序;否则,回退到ipcMain方法。
|
||||||
|
if (onNotificationNavigate) {
|
||||||
|
onNotificationNavigate(sessionId);
|
||||||
|
} else {
|
||||||
|
// 如果尚未设置处理程序,则通过ipcMain发出事件
|
||||||
|
// 正常流程中不应该发生这种情况,因为我们在初始化之前设置了处理程序。
|
||||||
|
console.warn(
|
||||||
|
"[NotificationWindow] onNotificationNavigate not set yet",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[NotificationWindow] Linux notification service initialized",
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"[NotificationWindow] Failed to initialize Linux notification service:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
win.setPosition(Math.floor(x), Math.floor(y))
|
ipcMain.handle("notification:show", (_, data) => {
|
||||||
win.setSize(winWidth, winHeight) // 确保尺寸
|
showNotification(data);
|
||||||
|
});
|
||||||
|
|
||||||
// 设为可交互
|
ipcMain.handle("notification:close", () => {
|
||||||
win.setIgnoreMouseEvents(false)
|
if (isLinux && linuxNotificationService) {
|
||||||
win.showInactive() // 显示但不聚焦
|
// 注册通知点击回调函数。Linux通知通过D-Bus自动关闭,但我们可以根据需要进行跟踪
|
||||||
win.setAlwaysOnTop(true, 'screen-saver') // 最高层级
|
return;
|
||||||
|
}
|
||||||
|
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||||
|
notificationWindow.hide();
|
||||||
|
notificationWindow.setIgnoreMouseEvents(true, { forward: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
win.webContents.send('notification:show', { ...data, position })
|
// Handle renderer ready event (fix race condition)
|
||||||
|
ipcMain.on("notification:ready", (event) => {
|
||||||
|
if (isLinux) {
|
||||||
|
// Linux不需要通知窗口,拦截通知窗口渲染
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("[NotificationWindow] Renderer ready, checking cached data");
|
||||||
|
if (
|
||||||
|
lastNotificationData &&
|
||||||
|
notificationWindow &&
|
||||||
|
!notificationWindow.isDestroyed()
|
||||||
|
) {
|
||||||
|
console.log("[NotificationWindow] Re-sending cached data");
|
||||||
|
notificationWindow.webContents.send(
|
||||||
|
"notification:show",
|
||||||
|
lastNotificationData,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 自动关闭计时器通常由渲染进程管理
|
// Handle resize request from renderer
|
||||||
// 渲染进程发送 'notification:close' 来隐藏窗口
|
ipcMain.on("notification:resize", (event, { width, height }) => {
|
||||||
}
|
if (isLinux) {
|
||||||
|
// Linux 通知通过D-Bus自动调整大小
|
||||||
export function registerNotificationHandlers() {
|
return;
|
||||||
ipcMain.handle('notification:show', (_, data) => {
|
}
|
||||||
showNotification(data)
|
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
||||||
})
|
// Enforce max-height if needed, or trust renderer
|
||||||
|
// Ensure it doesn't go off screen bottom?
|
||||||
ipcMain.handle('notification:close', () => {
|
// Logic in showAndSend handles position, but we need to keep anchor point (top-right usually).
|
||||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
// If we resize, we should re-calculate position to keep it anchored?
|
||||||
notificationWindow.hide()
|
// Actually, setSize changes size. If it's top-right, x/y stays same -> window grows down. That's fine for top-right.
|
||||||
notificationWindow.setIgnoreMouseEvents(true, { forward: true })
|
// If bottom-right, growing down pushes it off screen.
|
||||||
}
|
|
||||||
})
|
// Simple version: just setSize. For V1 we assume Top-Right.
|
||||||
|
// But wait, the config supports bottom-right.
|
||||||
// Handle renderer ready event (fix race condition)
|
// We can re-call setPosition or just let it be.
|
||||||
ipcMain.on('notification:ready', (event) => {
|
// If bottom-right, y needs to prevent overflow.
|
||||||
console.log('[NotificationWindow] Renderer ready, checking cached data')
|
|
||||||
if (lastNotificationData && notificationWindow && !notificationWindow.isDestroyed()) {
|
// Ideally we get current config position
|
||||||
console.log('[NotificationWindow] Re-sending cached data')
|
const bounds = notificationWindow.getBounds();
|
||||||
notificationWindow.webContents.send('notification:show', lastNotificationData)
|
// 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));
|
||||||
|
}
|
||||||
// Handle resize request from renderer
|
});
|
||||||
ipcMain.on('notification:resize', (event, { width, height }) => {
|
|
||||||
if (notificationWindow && !notificationWindow.isDestroyed()) {
|
// 'notification-clicked' 在 main.ts 中处理 (导航)
|
||||||
// Enforce max-height if needed, or trust renderer
|
|
||||||
// Ensure it doesn't go off screen bottom?
|
|
||||||
// Logic in showAndSend handles position, but we need to keep anchor point (top-right usually).
|
|
||||||
// If we resize, we should re-calculate position to keep it anchored?
|
|
||||||
// Actually, setSize changes size. If it's top-right, x/y stays same -> window grows down. That's fine for top-right.
|
|
||||||
// If bottom-right, growing down pushes it off screen.
|
|
||||||
|
|
||||||
// Simple version: just setSize. For V1 we assume Top-Right.
|
|
||||||
// But wait, the config supports bottom-right.
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 'notification-clicked' 在 main.ts 中处理 (导航)
|
|
||||||
}
|
}
|
||||||
|
|||||||
217
package-lock.json
generated
217
package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vscode/sudo-prompt": "^9.3.2",
|
"@vscode/sudo-prompt": "^9.3.2",
|
||||||
|
"dbus-native": "^0.4.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jieba-wasm": "^2.2.0",
|
"jieba-wasm": "^2.2.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"koffi": "^2.9.0",
|
"koffi": "^2.15.5",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
@@ -3083,6 +3084,25 @@
|
|||||||
"node": "^18.17.0 || >=20.5.0"
|
"node": "^18.17.0 || >=20.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/abstract-socket": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/abstract-socket/-/abstract-socket-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-YZJizsvS1aBua5Gd01woe4zuyYBGgSMeqDOB6/ChwdTI904KP6QGtJswXl4hcqWxbz86hQBe++HWV0hF1aGUtA==",
|
||||||
|
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.2.1",
|
||||||
|
"nan": "^2.12.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.4",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
@@ -3595,6 +3615,16 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bl": {
|
"node_modules/bl": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||||
@@ -4429,6 +4459,27 @@
|
|||||||
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
|
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dbus-native": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dbus-native/-/dbus-native-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-i3zvY3tdPEOaMgmK4riwupjDYRJ53rcE1Kj8rAgnLOFmBd0DekUih59qv8v+Oyils/U9p+s4sSsaBzHWLztI+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"event-stream": "^4.0.0",
|
||||||
|
"hexy": "^0.2.10",
|
||||||
|
"long": "^4.0.0",
|
||||||
|
"optimist": "^0.6.1",
|
||||||
|
"put": "0.0.6",
|
||||||
|
"safe-buffer": "^5.1.1",
|
||||||
|
"xml2js": "^0.4.17"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"dbus2js": "bin/dbus2js.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"abstract-socket": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debounce-fn": {
|
"node_modules/debounce-fn": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz",
|
||||||
@@ -4797,6 +4848,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/duplexer": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/duplexer2": {
|
"node_modules/duplexer2": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
||||||
@@ -5322,6 +5379,21 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/event-stream": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"duplexer": "^0.1.1",
|
||||||
|
"from": "^0.1.7",
|
||||||
|
"map-stream": "0.0.7",
|
||||||
|
"pause-stream": "^0.0.11",
|
||||||
|
"split": "^1.0.1",
|
||||||
|
"stream-combiner": "^0.2.2",
|
||||||
|
"through": "^2.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/exceljs": {
|
"node_modules/exceljs": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz",
|
||||||
@@ -5498,6 +5570,13 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/filelist": {
|
"node_modules/filelist": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
||||||
@@ -5585,6 +5664,12 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/from": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
|
||||||
|
"integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fs-constants": {
|
"node_modules/fs-constants": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
@@ -5984,6 +6069,15 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hexy": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/hexy/-/hexy-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-ciq6hFsSG/Bpt2DmrZJtv+56zpPdnq+NQ4ijEFrveKN0ZG1mhl/LdT1NQZ9se6ty1fACcI4d4vYqC9v8EYpH2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"hexy": "bin/hexy_cmd.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hosted-git-info": {
|
"node_modules/hosted-git-info": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
|
||||||
@@ -6537,9 +6631,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/koffi": {
|
"node_modules/koffi": {
|
||||||
"version": "2.15.2",
|
"version": "2.15.5",
|
||||||
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.5.tgz",
|
||||||
"integrity": "sha512-r9tjJLVRSOhCRWdVyQlF3/Ugzeg13jlzS4czS82MAgLff4W+BcYOW7g8Y62t9O5JYjYOLAjAovAZDNlDfZNu+g==",
|
"integrity": "sha512-4/35/oOpnH9tzrpWAC3ObjAERBSe0Q0Dh2NP1eBBPRGpohEj4vFw2+7tej9W9MTExvk0vtF0PjMqIGG4rf6feQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -6712,6 +6806,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/longest-streak": {
|
"node_modules/longest-streak": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
||||||
@@ -6774,6 +6874,12 @@
|
|||||||
"node": "^18.17.0 || >=20.5.0"
|
"node": "^18.17.0 || >=20.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/map-stream": {
|
||||||
|
"version": "0.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
|
||||||
|
"integrity": "sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/markdown-table": {
|
"node_modules/markdown-table": {
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||||
@@ -7917,6 +8023,13 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nan": {
|
||||||
|
"version": "2.26.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz",
|
||||||
|
"integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
@@ -8109,6 +8222,22 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/optimist": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==",
|
||||||
|
"license": "MIT/X11",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "~0.0.1",
|
||||||
|
"wordwrap": "~0.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/optimist/node_modules/minimist": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||||
|
"integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ora": {
|
"node_modules/ora": {
|
||||||
"version": "5.4.1",
|
"version": "5.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
|
||||||
@@ -8258,6 +8387,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/pause-stream": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||||
|
"integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==",
|
||||||
|
"license": [
|
||||||
|
"MIT",
|
||||||
|
"Apache2"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"through": "~2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pe-library": {
|
"node_modules/pe-library": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
|
||||||
@@ -8456,6 +8597,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/put": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/put/-/put-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-w0szIZ2NkqznMFqxYPRETCIi+q/S8UKis9F4yOl6/N9NDCZmbjZZT85aI4FgJf3vIPrzMPX60+odCLOaYxNWWw==",
|
||||||
|
"license": "MIT/X11",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/quick-lru": {
|
"node_modules/quick-lru": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
|
||||||
@@ -9317,6 +9467,18 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"through": "2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sprintf-js": {
|
"node_modules/sprintf-js": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||||
@@ -9348,6 +9510,16 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stream-combiner": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"duplexer": "~0.1.1",
|
||||||
|
"through": "~2.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
@@ -9616,6 +9788,12 @@
|
|||||||
"utrie": "^1.0.2"
|
"utrie": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/through": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tiny-async-pool": {
|
"node_modules/tiny-async-pool": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz",
|
||||||
@@ -10202,6 +10380,15 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wordwrap": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
@@ -10245,6 +10432,28 @@
|
|||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/xml2js": {
|
||||||
|
"version": "0.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
|
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xml2js/node_modules/xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xmlbuilder": {
|
"node_modules/xmlbuilder": {
|
||||||
"version": "15.1.1",
|
"version": "15.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vscode/sudo-prompt": "^9.3.2",
|
"@vscode/sudo-prompt": "^9.3.2",
|
||||||
|
"dbus-native": "^0.4.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"jieba-wasm": "^2.2.0",
|
"jieba-wasm": "^2.2.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"koffi": "^2.9.0",
|
"koffi": "^2.15.5",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
|
|||||||
52
src/App.tsx
52
src/App.tsx
@@ -670,32 +670,32 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showWaylandWarning && (
|
{/*{showWaylandWarning && (*/}
|
||||||
<div className="agreement-overlay">
|
{/* <div className="agreement-overlay">*/}
|
||||||
<div className="agreement-modal">
|
{/* <div className="agreement-modal">*/}
|
||||||
<div className="agreement-header">
|
{/* <div className="agreement-header">*/}
|
||||||
<Shield size={32} />
|
{/* <Shield size={32} />*/}
|
||||||
<h2>环境兼容性提示 (Wayland)</h2>
|
{/* <h2>环境兼容性提示 (Wayland)</h2>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
<div className="agreement-content">
|
{/* <div className="agreement-content">*/}
|
||||||
<div className="agreement-text">
|
{/* <div className="agreement-text">*/}
|
||||||
<p>检测到您当前正在使用 <strong>Wayland</strong> 显示服务器。</p>
|
{/* <p>检测到您当前正在使用 <strong>Wayland</strong> 显示服务器。</p>*/}
|
||||||
<p>在 Wayland 环境下,出于系统级的安全与设计机制,<strong>应用程序无法直接控制新弹出窗口的位置</strong>。</p>
|
{/* <p>在 Wayland 环境下,出于系统级的安全与设计机制,<strong>应用程序无法直接控制新弹出窗口的位置</strong>。</p>*/}
|
||||||
<p>这可能导致某些独立窗口(如消息通知、图片查看器等)出现位置随机、或不受控制的情况。这是底层机制导致的,对此我们无能为力。</p>
|
{/* <p>这可能导致某些独立窗口(如消息通知、图片查看器等)出现位置随机、或不受控制的情况。这是底层机制导致的,对此我们无能为力。</p>*/}
|
||||||
<br />
|
{/* <br />*/}
|
||||||
<p>如果您觉得窗口位置异常严重影响了使用体验,建议尝试:</p>
|
{/* <p>如果您觉得窗口位置异常严重影响了使用体验,建议尝试:</p>*/}
|
||||||
<p>1. 在系统登录界面,将会话切换回 <strong>X11 (Xorg)</strong> 模式。</p>
|
{/* <p>1. 在系统登录界面,将会话切换回 <strong>X11 (Xorg)</strong> 模式。</p>*/}
|
||||||
<p>2. 修改您的桌面管理器 (WM/DE) 配置,强制指定该应用程序的窗口规则。</p>
|
{/* <p>2. 修改您的桌面管理器 (WM/DE) 配置,强制指定该应用程序的窗口规则。</p>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
<div className="agreement-footer">
|
{/* <div className="agreement-footer">*/}
|
||||||
<div className="agreement-actions">
|
{/* <div className="agreement-actions">*/}
|
||||||
<button className="btn btn-primary" onClick={handleDismissWaylandWarning}>我知道了,不再提示</button>
|
{/* <button className="btn btn-primary" onClick={handleDismissWaylandWarning}>我知道了,不再提示</button>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
</div>
|
{/* </div>*/}
|
||||||
)}
|
{/*)}*/}
|
||||||
|
|
||||||
{/* 更新提示对话框 */}
|
{/* 更新提示对话框 */}
|
||||||
<UpdateDialog
|
<UpdateDialog
|
||||||
|
|||||||
Reference in New Issue
Block a user