🎨 Optimize multi-window services

This commit is contained in:
2025-11-05 00:10:26 +08:00
parent e9b6fef3cd
commit 1f0254822f
15 changed files with 220 additions and 239 deletions

View File

@@ -1,4 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export * from "./models.js";

View File

@@ -1,80 +0,0 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
/**
* ServiceOptions provides optional parameters for calls to [NewService].
*/
export class ServiceOptions {
/**
* Name can be set to override the name of the service
* for logging and debugging purposes.
*
* If empty, it will default
* either to the value obtained through the [ServiceName] interface,
* or to the type name.
*/
"Name": string;
/**
* If the service instance implements [http.Handler],
* it will be mounted on the internal asset server
* at the prefix specified by Route.
*/
"Route": string;
/**
* MarshalError will be called if non-nil
* to marshal to JSON the error values returned by this service's methods.
*
* MarshalError is not allowed to fail,
* but it may return a nil slice to fall back
* to the globally configured error handler.
*
* If the returned slice is not nil, it must contain valid JSON.
*/
"MarshalError": any;
/** Creates a new ServiceOptions instance. */
constructor($$source: Partial<ServiceOptions> = {}) {
if (!("Name" in $$source)) {
this["Name"] = "";
}
if (!("Route" in $$source)) {
this["Route"] = "";
}
if (!("MarshalError" in $$source)) {
this["MarshalError"] = null;
}
Object.assign(this, $$source);
}
/**
* Creates a new ServiceOptions instance from a string or object.
*/
static createFrom($$source: any = {}): ServiceOptions {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new ServiceOptions($$parsedSource as Partial<ServiceOptions>);
}
}
export class WebviewWindow {
/** Creates a new WebviewWindow instance. */
constructor($$source: Partial<WebviewWindow> = {}) {
Object.assign(this, $$source);
}
/**
* Creates a new WebviewWindow instance from a string or object.
*/
static createFrom($$source: any = {}): WebviewWindow {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new WebviewWindow($$parsedSource as Partial<WebviewWindow>);
}
}

View File

@@ -19,6 +19,7 @@ import * as ThemeService from "./themeservice.js";
import * as TranslationService from "./translationservice.js";
import * as TrayService from "./trayservice.js";
import * as WindowService from "./windowservice.js";
import * as WindowSnapService from "./windowsnapservice.js";
export {
BackupService,
ConfigService,
@@ -37,7 +38,8 @@ export {
ThemeService,
TranslationService,
TrayService,
WindowService
WindowService,
WindowSnapService
};
export * from "./models.js";

View File

@@ -5,9 +5,6 @@
// @ts-ignore: Unused imports
import {Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as http$0 from "../../../net/http/models.js";
@@ -394,42 +391,6 @@ export class SystemInfo {
}
}
/**
* WindowInfo 窗口信息
*/
export class WindowInfo {
"Window": application$0.WebviewWindow | null;
"DocumentID": number;
"Title": string;
/** Creates a new WindowInfo instance. */
constructor($$source: Partial<WindowInfo> = {}) {
if (!("Window" in $$source)) {
this["Window"] = null;
}
if (!("DocumentID" in $$source)) {
this["DocumentID"] = 0;
}
if (!("Title" in $$source)) {
this["Title"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new WindowInfo instance from a string or object.
*/
static createFrom($$source: any = {}): WindowInfo {
const $$createField0_0 = $$createType8;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("Window" in $$parsedSource) {
$$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]);
}
return new WindowInfo($$parsedSource as Partial<WindowInfo>);
}
}
/**
* WindowSnapService 窗口吸附服务
*/
@@ -463,5 +424,3 @@ const $$createType3 = $Create.Map($Create.Any, $$createType2);
const $$createType4 = OSInfo.createFrom;
const $$createType5 = $Create.Nullable($$createType4);
const $$createType6 = $Create.Map($Create.Any, $Create.Any);
const $$createType7 = application$0.WebviewWindow.createFrom;
const $$createType8 = $Create.Nullable($$createType7);

View File

@@ -2,7 +2,7 @@
// This file is automatically generated. DO NOT EDIT
/**
* WindowService 窗口管理服务(专注于窗口生命周期管理)
* WindowService 窗口管理服务
* @module
*/
@@ -10,17 +10,33 @@
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* GetOpenWindows 获取所有打开的窗口信息
* GetOpenDocumentWindows 获取所有文档窗口
*/
export function GetOpenWindows(): Promise<$models.WindowInfo[]> & { cancel(): void } {
export function GetOpenDocumentWindows(): Promise<application$0.Window[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(3057936408) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType0($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* GetOpenWindows 获取所有打开的文档窗口
*/
export function GetOpenWindows(): Promise<application$0.Window[]> & { cancel(): void } {
let $resultPromise = $Call.ByID(1464997251) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType1($result);
return $$createType0($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
@@ -59,5 +75,4 @@ export function SetWindowSnapService(snapService: $models.WindowSnapService | nu
}
// Private type creation functions
const $$createType0 = $models.WindowInfo.createFrom;
const $$createType1 = $Create.Array($$createType0);
const $$createType0 = $Create.Array($Create.Any);

View File

@@ -0,0 +1,94 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* WindowSnapService 窗口吸附服务
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../../../github.com/wailsapp/wails/v3/pkg/application/models.js";
/**
* Cleanup 清理资源
*/
export function Cleanup(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2155505498) as any;
return $resultPromise;
}
/**
* GetCurrentThreshold 获取当前自适应阈值(用于调试或显示)
*/
export function GetCurrentThreshold(): Promise<number> & { cancel(): void } {
let $resultPromise = $Call.ByID(3176419026) as any;
return $resultPromise;
}
/**
* GetDiagnosticInfo 获取诊断信息(用于调试)
*/
export function GetDiagnosticInfo(): Promise<{ [_: string]: any }> & { cancel(): void } {
let $resultPromise = $Call.ByID(39381769) as any;
let $typingPromise = $resultPromise.then(($result: any) => {
return $$createType0($result);
}) as any;
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* OnWindowSnapConfigChanged 处理窗口吸附配置变更
*/
export function OnWindowSnapConfigChanged(enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(3794787039, enabled) as any;
return $resultPromise;
}
/**
* RegisterWindow 注册需要吸附管理的窗口
*/
export function RegisterWindow(documentID: number, window: application$0.WebviewWindow | null): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1000222723, documentID, window) as any;
return $resultPromise;
}
/**
* ServiceShutdown 实现服务关闭接口
*/
export function ServiceShutdown(): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(1172710495) as any;
return $resultPromise;
}
/**
* ServiceStartup 服务启动时初始化
*/
export function ServiceStartup(options: application$0.ServiceOptions): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2456823262, options) as any;
return $resultPromise;
}
/**
* SetSnapEnabled 设置是否启用窗口吸附
*/
export function SetSnapEnabled(enabled: boolean): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2280126835, enabled) as any;
return $resultPromise;
}
/**
* UnregisterWindow 取消注册窗口
*/
export function UnregisterWindow(documentID: number): Promise<void> & { cancel(): void } {
let $resultPromise = $Call.ByID(2844230768, documentID) as any;
return $resultPromise;
}
// Private type creation functions
const $$createType0 = $Create.Map($Create.Any, $Create.Any);

View File

@@ -32,7 +32,6 @@ type SnapPosition struct {
// WindowInfo 窗口信息
type WindowInfo struct {
DocumentID int64 `json:"documentID"` // 文档ID
Title string `json:"title"` // 窗口标题
IsSnapped bool `json:"isSnapped"` // 是否处于吸附状态
SnapOffset SnapPosition `json:"snapOffset"` // 与主窗口的相对位置偏移
SnapEdge SnapEdge `json:"snapEdge"` // 吸附的边缘类型

View File

@@ -255,35 +255,26 @@ func (hs *HotkeyService) isWindowVisible(window *application.WebviewWindow) bool
// hideAllWindows 隐藏所有窗口
func (hs *HotkeyService) hideAllWindows() {
// 隐藏主窗口
hs.windowHelper.HideMainWindow()
// 隐藏所有子窗口
if hs.windowService != nil {
openWindows := hs.windowService.GetOpenWindows()
for _, windowInfo := range openWindows {
windowInfo.Window.Hide()
windowInfo.Hide()
}
}
hs.logger.Debug("all windows hidden")
}
// showAllWindows 显示所有窗口
func (hs *HotkeyService) showAllWindows() {
// 显示主窗口
hs.windowHelper.FocusMainWindow()
// 显示所有子窗口
if hs.windowService != nil {
openWindows := hs.windowService.GetOpenWindows()
for _, windowInfo := range openWindows {
windowInfo.Window.Show()
windowInfo.Window.Restore()
windowInfo.Show()
windowInfo.Restore()
}
}
hs.logger.Debug("all windows shown")
}
// keyToVirtualKeyCode 键名转虚拟键码

View File

@@ -337,35 +337,26 @@ func (hs *HotkeyService) isWindowVisible(window *application.WebviewWindow) bool
// hideAllWindows 隐藏所有窗口
func (hs *HotkeyService) hideAllWindows() {
// 隐藏主窗口
hs.windowHelper.HideMainWindow()
// 隐藏所有子窗口
if hs.windowService != nil {
openWindows := hs.windowService.GetOpenWindows()
for _, windowInfo := range openWindows {
windowInfo.Window.Hide()
windowInfo.Hide()
}
}
hs.logger.Debug("all windows hidden")
}
// showAllWindows 显示所有窗口
func (hs *HotkeyService) showAllWindows() {
// 显示主窗口
hs.windowHelper.FocusMainWindow()
// 显示所有子窗口
if hs.windowService != nil {
openWindows := hs.windowService.GetOpenWindows()
for _, windowInfo := range openWindows {
windowInfo.Window.Show()
windowInfo.Window.Restore()
windowInfo.Show()
windowInfo.Restore()
}
}
hs.logger.Debug("all windows shown")
}
// ServiceShutdown 关闭热键服务

View File

@@ -372,35 +372,26 @@ func (hs *HotkeyService) isWindowVisible(window *application.WebviewWindow) bool
// hideAllWindows 隐藏所有窗口
func (hs *HotkeyService) hideAllWindows() {
// 隐藏主窗口
hs.windowHelper.HideMainWindow()
// 隐藏所有子窗口
if hs.windowService != nil {
openWindows := hs.windowService.GetOpenWindows()
for _, windowInfo := range openWindows {
windowInfo.Window.Hide()
windowInfo.Hide()
}
}
hs.logger.Debug("all windows hidden")
}
// showAllWindows 显示所有窗口
func (hs *HotkeyService) showAllWindows() {
// 显示主窗口
hs.windowHelper.FocusMainWindow()
// 显示所有子窗口
if hs.windowService != nil {
openWindows := hs.windowService.GetOpenWindows()
for _, windowInfo := range openWindows {
windowInfo.Window.Show()
windowInfo.Window.Restore()
windowInfo.Show()
windowInfo.Restore()
}
}
hs.logger.Debug("all windows shown")
}
// keyToX11KeyCode 键名转X11键码

View File

@@ -145,6 +145,7 @@ func NewServiceManager() *ServiceManager {
configService: configService,
databaseService: databaseService,
documentService: documentService,
windowSnapService: windowSnapService,
windowService: windowService,
migrationService: migrationService,
systemService: systemService,
@@ -189,6 +190,7 @@ func (sm *ServiceManager) GetServices() []application.Service {
application.NewService(sm.testService),
application.NewService(sm.BackupService),
application.NewService(sm.httpClientService),
application.NewService(sm.windowSnapService),
}
return services
}

View File

@@ -1,6 +1,8 @@
package services
import (
"strconv"
"github.com/wailsapp/wails/v3/pkg/application"
"voidraft/internal/common/constant"
)
@@ -77,3 +79,45 @@ func (wh *WindowHelper) AutoShowMainWindow() {
window.Show()
}
}
// GetDocumentWindow 根据文档ID获取窗口利用 Wails3 的 WindowManager
func (wh *WindowHelper) GetDocumentWindow(documentID int64) (application.Window, bool) {
app := application.Get()
windowName := strconv.FormatInt(documentID, 10)
return app.Window.GetByName(windowName)
}
// GetAllDocumentWindows 获取所有文档窗口(排除主窗口)
func (wh *WindowHelper) GetAllDocumentWindows() []application.Window {
app := application.Get()
allWindows := app.Window.GetAll()
var docWindows []application.Window
for _, window := range allWindows {
// 跳过主窗口
if window.Name() != constant.VOIDRAFT_MAIN_WINDOW_NAME {
docWindows = append(docWindows, window)
}
}
return docWindows
}
// FocusDocumentWindow 聚焦指定文档的窗口
func (wh *WindowHelper) FocusDocumentWindow(documentID int64) bool {
if window, exists := wh.GetDocumentWindow(documentID); exists {
window.Show()
window.Restore()
window.Focus()
return true
}
return false
}
// CloseDocumentWindow 关闭指定文档的窗口
func (wh *WindowHelper) CloseDocumentWindow(documentID int64) bool {
if window, exists := wh.GetDocumentWindow(documentID); exists {
window.Close()
return true
}
return false
}

View File

@@ -3,7 +3,6 @@ package services
import (
"fmt"
"strconv"
"sync"
"voidraft/internal/common/constant"
"github.com/wailsapp/wails/v3/pkg/application"
@@ -11,20 +10,10 @@ import (
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// WindowInfo 窗口信息
type WindowInfo struct {
Window *application.WebviewWindow
DocumentID int64
Title string
}
// WindowService 窗口管理服务(专注于窗口生命周期管理)
// WindowService 窗口管理服务
type WindowService struct {
logger *log.LogService
documentService *DocumentService
windows map[int64]*WindowInfo // documentID -> WindowInfo
mu sync.RWMutex
// 吸附服务引用
windowSnapService *WindowSnapService
}
@@ -38,7 +27,6 @@ func NewWindowService(logger *log.LogService, documentService *DocumentService)
return &WindowService{
logger: logger,
documentService: documentService,
windows: make(map[int64]*WindowInfo),
}
}
@@ -49,15 +37,14 @@ func (ws *WindowService) SetWindowSnapService(snapService *WindowSnapService) {
// OpenDocumentWindow 为指定文档ID打开新窗口
func (ws *WindowService) OpenDocumentWindow(documentID int64) error {
ws.mu.Lock()
defer ws.mu.Unlock()
app := application.Get()
windowName := strconv.FormatInt(documentID, 10)
// 检查窗口是否已经存在
if windowInfo, exists := ws.windows[documentID]; exists {
if existingWindow, exists := app.Window.GetByName(windowName); exists {
// 窗口已存在,显示并聚焦
windowInfo.Window.Show()
windowInfo.Window.Restore()
windowInfo.Window.Focus()
existingWindow.Show()
existingWindow.Restore()
existingWindow.Focus()
return nil
}
@@ -70,10 +57,9 @@ func (ws *WindowService) OpenDocumentWindow(documentID int64) error {
return fmt.Errorf("document not found: %d", documentID)
}
app := application.Get()
// 创建新窗口
newWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: strconv.FormatInt(doc.ID, 10),
Name: windowName,
Title: fmt.Sprintf("voidraft - %s", doc.Title),
Width: constant.VOIDRAFT_WINDOW_WIDTH,
Height: constant.VOIDRAFT_WINDOW_HEIGHT,
@@ -93,26 +79,17 @@ func (ws *WindowService) OpenDocumentWindow(documentID int64) error {
URL: fmt.Sprintf("/?documentId=%d", documentID),
})
newWindow.Center()
app.Window.Add(newWindow)
// 保存窗口信息
windowInfo := &WindowInfo{
Window: newWindow,
DocumentID: documentID,
Title: doc.Title,
}
ws.windows[documentID] = windowInfo
// 注册窗口事件
ws.registerWindowEvents(newWindow, documentID)
// 向吸附服务注册新窗口
if ws.windowSnapService != nil {
ws.windowSnapService.RegisterWindow(documentID, newWindow, doc.Title)
ws.windowSnapService.RegisterWindow(documentID, newWindow)
}
// 最后才移动窗口到中心
newWindow.Center()
return nil
}
@@ -126,53 +103,50 @@ func (ws *WindowService) registerWindowEvents(window *application.WebviewWindow,
// onWindowClosing 处理窗口关闭事件
func (ws *WindowService) onWindowClosing(documentID int64) {
ws.mu.Lock()
defer ws.mu.Unlock()
windowInfo, exists := ws.windows[documentID]
if exists {
windowInfo.Window.Close()
delete(ws.windows, documentID)
// 从吸附服务中取消注册
if ws.windowSnapService != nil {
ws.windowSnapService.UnregisterWindow(documentID)
}
// 从吸附服务中取消注册
if ws.windowSnapService != nil {
ws.windowSnapService.UnregisterWindow(documentID)
}
}
// GetOpenWindows 获取所有打开的窗口信息
func (ws *WindowService) GetOpenWindows() []WindowInfo {
ws.mu.RLock()
defer ws.mu.RUnlock()
// GetOpenWindows 获取所有打开的文档窗口
func (ws *WindowService) GetOpenWindows() []application.Window {
app := application.Get()
return app.Window.GetAll()
}
var windows []WindowInfo
for _, windowInfo := range ws.windows {
windows = append(windows, *windowInfo)
// GetOpenDocumentWindows 获取所有文档窗口
func (ws *WindowService) GetOpenDocumentWindows() []application.Window {
app := application.Get()
allWindows := app.Window.GetAll()
var docWindows []application.Window
for _, window := range allWindows {
if window.Name() != constant.VOIDRAFT_MAIN_WINDOW_NAME {
docWindows = append(docWindows, window)
}
}
return windows
return docWindows
}
// IsDocumentWindowOpen 检查指定文档的窗口是否已打开
func (ws *WindowService) IsDocumentWindowOpen(documentID int64) bool {
ws.mu.RLock()
defer ws.mu.RUnlock()
_, exists := ws.windows[documentID]
app := application.Get()
windowName := strconv.FormatInt(documentID, 10)
_, exists := app.Window.GetByName(windowName)
return exists
}
// ServiceShutdown 实现服务关闭接口
func (ws *WindowService) ServiceShutdown() error {
// 关闭所有窗口
ws.mu.Lock()
defer ws.mu.Unlock()
for documentID := range ws.windows {
if ws.windowSnapService != nil {
ws.windowSnapService.UnregisterWindow(documentID)
// 从吸附服务中取消注册所有窗口
if ws.windowSnapService != nil {
windows := ws.GetOpenDocumentWindows()
for _, window := range windows {
if documentID, err := strconv.ParseInt(window.Name(), 10, 64); err == nil {
ws.windowSnapService.UnregisterWindow(documentID)
}
}
}
return nil
}

View File

@@ -74,16 +74,18 @@ func (wss *WindowSnapService) ServiceStartup(ctx context.Context, options applic
}
// RegisterWindow 注册需要吸附管理的窗口
func (wss *WindowSnapService) RegisterWindow(documentID int64, window *application.WebviewWindow, title string) {
func (wss *WindowSnapService) RegisterWindow(documentID int64, window *application.WebviewWindow) {
wss.mu.Lock()
defer wss.mu.Unlock()
wss.logger.Info("[WindowSnap] RegisterWindow - DocumentID: %d, SnapEnabled: %v", documentID, wss.snapEnabled)
// 获取初始位置
x, y := window.Position()
wss.logger.Info("[WindowSnap] Initial position - X: %d, Y: %d", x, y)
windowInfo := &models.WindowInfo{
DocumentID: documentID,
Title: title,
IsSnapped: false,
SnapOffset: models.SnapPosition{X: 0, Y: 0},
SnapEdge: models.SnapEdgeNone,
@@ -94,6 +96,8 @@ func (wss *WindowSnapService) RegisterWindow(documentID int64, window *applicati
wss.managedWindows[documentID] = windowInfo
wss.windowRefs[documentID] = window
wss.logger.Info("[WindowSnap] Managed windows count: %d", len(wss.managedWindows))
// 为窗口设置移动事件监听
wss.setupWindowEvents(window, windowInfo)
}

View File

@@ -91,7 +91,6 @@ func main() {
})
mainWindow.Center()
window = mainWindow
trayService := serviceManager.GetTrayService()
// 设置系统托盘
systray.SetupSystemTray(mainWindow, assets, trayService)