Skip to content

Comments

feat: 添加日志悬浮窗功能#65

Open
niechy wants to merge 5 commits intoMistEO:mainfrom
niechy:feat/log-overlay
Open

feat: 添加日志悬浮窗功能#65
niechy wants to merge 5 commits intoMistEO:mainfrom
niechy:feat/log-overlay

Conversation

@niechy
Copy link
Contributor

@niechy niechy commented Feb 12, 2026

Summary

  • 新增日志悬浮窗功能,支持定点悬浮和跟随连接窗口两种模式
  • 跟随模式支持左中、右上(1/3处)、右下(2/3处)、上中四种锚定位置,悬浮窗贴在游戏窗口内侧边缘
  • 支持「最置顶」和「连接窗口上一层」两种层级模式
  • 使用 DWM API (DWMWA_EXTENDED_FRAME_BOUNDS) 获取精确可见窗口边界
  • 使用 PhysicalPosition + GetDpiForWindow 正确处理 DPI 缩放
  • 窗口句柄存储在 Rust 后端 (InstanceRuntime),前端按需查询,避免时序问题
  • 悬浮窗可拖拽、可缩放(右下角 resize grip),关闭时保存尺寸
  • 定点模式下检测屏幕范围,防止悬浮窗在屏幕外
  • 控制器未连接时不显示,主窗口关闭/最小化到托盘时自动关闭
  • 支持中英文 i18n

文件变更

新增文件

  • log-overlay.html - 悬浮窗 HTML 入口
  • src/logOverlay.tsx - 悬浮窗 React 入口
  • src/components/LogOverlay.tsx - 悬浮窗 UI 组件
  • src/services/logOverlayService.ts - 悬浮窗服务(创建/关闭/轮询/锚点计算)
  • src-tauri/capabilities/log-overlay.json - 悬浮窗权限配置

Rust 后端

  • system.rs - 新增 create/close/get_rect/set_zorder/set_always_on_top 等命令
  • types.rs - InstanceRuntime 新增 connected_window_handle 字段
  • maa_core.rs - 连接控制器时保存窗口句柄
  • lib.rs - 悬浮窗关闭事件处理、主窗口联动关闭

前端

  • GeneralSection.tsx - 设置页新增悬浮窗模式/锚点/层级选项
  • LogsPanel.tsx - 日志面板标题栏新增悬浮窗开关按钮
  • appStore.ts / types.ts / config.ts - 状态管理和配置持久化

Test plan

  • 连接 Win32 控制器后悬浮窗自动显示
  • 定点模式:可拖拽,位置固定不跟随
  • 跟随模式:四种锚定位置正确贴合游戏窗口内侧
  • 最置顶 / 连接窗口上一层切换正常
  • 关闭悬浮窗后主界面开关同步
  • 缩放悬浮窗后重新打开保持尺寸
  • 主窗口关闭/最小化到托盘时悬浮窗关闭
  • DPI 缩放 (125%/150%) 下位置正确

Made with Cursor

Summary by Sourcery

添加一个可配置的日志悬浮窗,使其可以浮动在已连接的游戏窗口附近,并将其与连接生命周期和设置持久化集成。

New Features:

  • 引入一个专用的日志悬浮窗口,作为独立的 Tauri webview 实现,具有可拖拽、可调整大小的透明界面,用于显示最近的日志。
  • 在前端日志面板中增加控制项和弹出层(popover),用于启用日志悬浮窗、配置模式/锚点/窗体层级(z-order),以及管理“跟随窗口”行为。
  • 在应用配置中持久化日志悬浮窗的设置(启用状态、模式、锚点、窗体层级、尺寸和位置),并在启动时恢复。

Enhancements:

  • 扩展 Rust 后端和 Tauri 命令,用于管理悬浮窗的生命周期、窗体层级(z-order)、窗口边界、支持 DPI 的定位,以及用于跟随模式的每个实例的已连接窗口句柄。
  • 通过 Tauri 事件,将日志事件和清除操作从主 UI store 流式传输到悬浮窗,使悬浮窗显示最新日志。
  • 订阅连接状态变化,使得当有控制器连接时悬浮窗自动显示,并在所有连接关闭时自动隐藏。
  • 优化 Vite 构建,为主应用和日志悬浮窗分别生成独立入口,并微调构建选项以提升体积和性能。
  • 为设置界面中日志悬浮窗的配置新增国际化字符串,支持中文、英文、日文和韩文。

Build:

  • 配置 Vite rollup inputs,为日志悬浮窗构建独立的 bundle 和 HTML 入口,与主应用一同打包。
Original summary in English

Summary by Sourcery

Add a configurable log overlay window that can float near connected game windows and integrate it with connection lifecycle and settings persistence.

New Features:

  • Introduce a dedicated log overlay window implemented as a separate Tauri webview with draggable and resizable transparent UI showing recent logs.
  • Add front-end controls and a popover in the logs panel to enable, configure mode/anchor/z-order, and manage follow-window behavior for the log overlay.
  • Persist log overlay settings (enable state, mode, anchor, z-order, size, and position) in the app configuration and restore them on startup.

Enhancements:

  • Extend the Rust backend and Tauri commands to manage overlay lifecycle, z-order, window bounds, DPI-aware positioning, and per-instance connected window handles for follow mode.
  • Stream log events and clear actions from the main UI store to the overlay window via Tauri events so the overlay displays up-to-date logs.
  • Subscribe to connection status changes so the overlay automatically appears when a controller connects and hides when all connections are closed.
  • Optimize the Vite build to produce separate entry points for the main app and the log overlay HTML and tweak build options for size and performance.
  • Add i18n strings for log overlay configuration in Chinese, English, Japanese, and Korean settings UIs.

Build:

  • Configure Vite rollup inputs to build a separate bundle and HTML entry for the log overlay window alongside the main app.
Original summary in English

Summary by Sourcery

添加一个可配置的日志悬浮窗,使其可以浮动在已连接的游戏窗口附近,并将其与连接生命周期和设置持久化集成。

New Features:

  • 引入一个专用的日志悬浮窗口,作为独立的 Tauri webview 实现,具有可拖拽、可调整大小的透明界面,用于显示最近的日志。
  • 在前端日志面板中增加控制项和弹出层(popover),用于启用日志悬浮窗、配置模式/锚点/窗体层级(z-order),以及管理“跟随窗口”行为。
  • 在应用配置中持久化日志悬浮窗的设置(启用状态、模式、锚点、窗体层级、尺寸和位置),并在启动时恢复。

Enhancements:

  • 扩展 Rust 后端和 Tauri 命令,用于管理悬浮窗的生命周期、窗体层级(z-order)、窗口边界、支持 DPI 的定位,以及用于跟随模式的每个实例的已连接窗口句柄。
  • 通过 Tauri 事件,将日志事件和清除操作从主 UI store 流式传输到悬浮窗,使悬浮窗显示最新日志。
  • 订阅连接状态变化,使得当有控制器连接时悬浮窗自动显示,并在所有连接关闭时自动隐藏。
  • 优化 Vite 构建,为主应用和日志悬浮窗分别生成独立入口,并微调构建选项以提升体积和性能。
  • 为设置界面中日志悬浮窗的配置新增国际化字符串,支持中文、英文、日文和韩文。

Build:

  • 配置 Vite rollup inputs,为日志悬浮窗构建独立的 bundle 和 HTML 入口,与主应用一同打包。
Original summary in English

Summary by Sourcery

Add a configurable log overlay window that can float near connected game windows and integrate it with connection lifecycle and settings persistence.

New Features:

  • Introduce a dedicated log overlay window implemented as a separate Tauri webview with draggable and resizable transparent UI showing recent logs.
  • Add front-end controls and a popover in the logs panel to enable, configure mode/anchor/z-order, and manage follow-window behavior for the log overlay.
  • Persist log overlay settings (enable state, mode, anchor, z-order, size, and position) in the app configuration and restore them on startup.

Enhancements:

  • Extend the Rust backend and Tauri commands to manage overlay lifecycle, z-order, window bounds, DPI-aware positioning, and per-instance connected window handles for follow mode.
  • Stream log events and clear actions from the main UI store to the overlay window via Tauri events so the overlay displays up-to-date logs.
  • Subscribe to connection status changes so the overlay automatically appears when a controller connects and hides when all connections are closed.
  • Optimize the Vite build to produce separate entry points for the main app and the log overlay HTML and tweak build options for size and performance.
  • Add i18n strings for log overlay configuration in Chinese, English, Japanese, and Korean settings UIs.

Build:

  • Configure Vite rollup inputs to build a separate bundle and HTML entry for the log overlay window alongside the main app.

- 支持定点悬浮和跟随连接窗口两种模式
- 跟随模式支持左中/右上/右下/上中四种锚定位置
- 支持最置顶和连接窗口上一层两种层级模式
- 使用 DWM API 获取精确窗口边界,PhysicalPosition 处理 DPI 缩放
- 悬浮窗可拖拽、可缩放,关闭时保存尺寸
- 定点模式检测屏幕范围,防止悬浮窗在屏幕外
- 控制器未连接时不显示悬浮窗
- 主窗口关闭/最小化到托盘时自动关闭悬浮窗
- 支持中英文 i18n

Co-authored-by: Cursor <cursoragent@cursor.com>
- 修复 macOS 构建:transparent() 仅在 Windows 上使用
- Cargo.toml 补全 Win32_Graphics_Dwm 和 Win32_UI_HiDpi features
- 新增 set_connected_window_handle 命令,支持 ADB 手动指定跟随窗口
- LogsPanel 添加窗口选择器(ADB + 跟随模式时显示)
- 「最置顶」改为「置顶」
- 补全 zh-TW、ja-JP、ko-KR 的日志悬浮窗 i18n

Co-authored-by: Cursor <cursoragent@cursor.com>
@niechy
Copy link
Contributor Author

niechy commented Feb 13, 2026

在非安卓控制器下感觉还行。
image
image
但如果是安卓控制器的跟随窗口模式下,要手动选窗口,看上去丑丑的,佬们来点ui建议
image

niechy and others added 2 commits February 13, 2026 16:37
- 修复 DPI 缩放累积导致悬浮窗每次打开变大的问题
- 修复 popover 关闭时意外触发侧面板折叠(React Portal 事件冒泡)
- 修复 popover 定位不正确和无法显示的问题
- 保存/恢复悬浮窗位置,启动时不再总是出现在左上角
- 将悬浮窗设置统一到 LogOverlayPopover 弹层
- 优化悬浮窗按钮视觉,增加状态指示

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@niechy
Copy link
Contributor Author

niechy commented Feb 13, 2026

从设置中移除了悬浮窗相关设置改到了主界面
image
image
image

@niechy niechy marked this pull request as ready for review February 13, 2026 08:54
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些高层次的反馈:

  • logOverlayService.clampToScreen 中,你假设 availableMonitors() 返回的显示器位置/尺寸与浮层窗口处于同一个(物理)坐标空间,但 Tauri 的显示器 API 是感知逻辑 DPI 的,而你在其他地方则将坐标当作物理像素来处理;为避免在非 100% DPI 下错误地把窗口判定为在屏幕外,建议显式地将所有坐标统一到同一个空间(逻辑或物理)。
  • subscribeConnectionStatus 这个 selector 在 equalityFn 中使用了 JSON.stringify,这会在每次 store 变更时运行并产生新的字符串;你可以通过做一次更便宜的比较来降低开销(例如单独跟踪一个派生出来的 hasAnyConnection 布尔值和 enabled 标志),而不是序列化整个对象。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `logOverlayService.clampToScreen` you assume monitor positions/sizes from `availableMonitors()` are in the same (physical) coordinate space as the overlay window, but Tauri monitor APIs are logical-DPI–aware while you otherwise treat coordinates as physical; consider explicitly normalizing everything to one space (logical or physical) to avoid misdetecting off-screen windows at non-100% DPI.
- The `subscribeConnectionStatus` selector uses `JSON.stringify` in `equalityFn`, which will run on every store change and allocate strings; you can reduce overhead by doing a cheap comparison (e.g. track a derived `hasAnyConnection` boolean and `enabled` flag) instead of serializing the whole object.

## Individual Comments

### Comment 1
<location> `src/components/LogOverlay.tsx:147-149` </location>
<code_context>
+          scrollbarColor: 'rgba(255,255,255,0.15) transparent',
+        }}
+      >
+        {logs.length === 0 ? (
+          <div className="h-full flex items-center justify-center text-white/25 text-[11px]">
+            等待日志...
+          </div>
+        ) : (
</code_context>

<issue_to_address>
**suggestion:** 浮层中的空状态文案是硬编码的中文;建议使用 i18n 字符串以保持一致性。

由于其他界面文案已经使用了 i18n 系统,这个浮层也应该从翻译文件中读取占位文案,从而在不同语言环境下都能正常工作(或者至少提供英文回退文案)。

建议的实现方式:

```typescript
          <div className="h-full flex items-center justify-center text-white/25 text-[11px]">
            {t('logOverlay.waitingLogs', 'Waiting for logs...')}
          </div>

```

要完整实现这项改动,你还需要:
1. 确保在 `src/components/LogOverlay.tsx` 顶部导入 `useTranslation`,例如:
   - `import { useTranslation } from 'react-i18next';` 或者项目正在使用的其他 i18n 库的对应导入方式。
2.`LogOverlay` 组件中初始化翻译 hook,使 `t` 可以在这段 JSX 中使用:
   - 例如在组件内部:`const { t } = useTranslation();`,或带上合适的 namespace:`useTranslation('logOverlay');`3. 在你的翻译 JSON 文件中添加对应的 key,例如:
   -`en/logOverlay.json`(或对应的 namespace 文件)中:  
     `{ "waitingLogs": "Waiting for logs..." }`
   - 如果需要,在中文翻译文件中:  
     `{ "waitingLogs": "等待日志..." }`
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,请考虑分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • In logOverlayService.clampToScreen you assume monitor positions/sizes from availableMonitors() are in the same (physical) coordinate space as the overlay window, but Tauri monitor APIs are logical-DPI–aware while you otherwise treat coordinates as physical; consider explicitly normalizing everything to one space (logical or physical) to avoid misdetecting off-screen windows at non-100% DPI.
  • The subscribeConnectionStatus selector uses JSON.stringify in equalityFn, which will run on every store change and allocate strings; you can reduce overhead by doing a cheap comparison (e.g. track a derived hasAnyConnection boolean and enabled flag) instead of serializing the whole object.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `logOverlayService.clampToScreen` you assume monitor positions/sizes from `availableMonitors()` are in the same (physical) coordinate space as the overlay window, but Tauri monitor APIs are logical-DPI–aware while you otherwise treat coordinates as physical; consider explicitly normalizing everything to one space (logical or physical) to avoid misdetecting off-screen windows at non-100% DPI.
- The `subscribeConnectionStatus` selector uses `JSON.stringify` in `equalityFn`, which will run on every store change and allocate strings; you can reduce overhead by doing a cheap comparison (e.g. track a derived `hasAnyConnection` boolean and `enabled` flag) instead of serializing the whole object.

## Individual Comments

### Comment 1
<location> `src/components/LogOverlay.tsx:147-149` </location>
<code_context>
+          scrollbarColor: 'rgba(255,255,255,0.15) transparent',
+        }}
+      >
+        {logs.length === 0 ? (
+          <div className="h-full flex items-center justify-center text-white/25 text-[11px]">
+            等待日志...
+          </div>
+        ) : (
</code_context>

<issue_to_address>
**suggestion:** The empty-state text in the overlay is hardcoded Chinese; consider using i18n strings for consistency.

Since other UI text uses the i18n system, this overlay should also read its placeholder from the translation files so it works across locales (or at least use an English fallback).

Suggested implementation:

```typescript
          <div className="h-full flex items-center justify-center text-white/25 text-[11px]">
            {t('logOverlay.waitingLogs', 'Waiting for logs...')}
          </div>

```

To fully implement this change, you will also need to:
1. Ensure `useTranslation` is imported at the top of `src/components/LogOverlay.tsx`, e.g.:
   - `import { useTranslation } from 'react-i18next';` or whichever i18n library the project uses.
2. Initialize the translation hook in the `LogOverlay` component so that `t` is in scope for this JSX:
   - e.g. inside the component: `const { t } = useTranslation();` or with the appropriate namespace: `useTranslation('logOverlay');`.
3. Add the corresponding key to your translation JSON files, for example:
   - In `en/logOverlay.json` (or the relevant namespace file):  
     `{ "waitingLogs": "Waiting for logs..." }`
   - And, if desired, in the Chinese translation file:  
     `{ "waitingLogs": "等待日志..." }`
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- 为 clampToScreen 添加坐标空间注释,明确物理像素一致性
- subscribeConnectionStatus 用轻量布尔比较替代 JSON.stringify
- 悬浮窗空状态文案改用 i18n,入口初始化 i18n

Co-authored-by: Cursor <cursoragent@cursor.com>
@MistEO
Copy link
Owner

MistEO commented Feb 15, 2026

太丑了,不做这玩意(

@niechy
Copy link
Contributor Author

niechy commented Feb 15, 2026

我是感觉先整一个能用的也行,以后再去改美观之类的,能实时显示点信息还是有用的。
如果有需要前台的功能,又有需要知道目前运行状态。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants