Files
Haocode/ui/views/system_tools/desktop_session.py
T
sorrow404null 0a62877cde feat(ui): cross-platform screenshot hotkey and portal-based capture
Windows keeps the legacy path; Linux X11 uses a native XGrabKey hotkey (ctypes libX11) with native capture; Wayland captures through xdg-desktop-portal and degrades gracefully with explicit capability-unavailable logs when the portal/protocol is missing.
2026-09-17 16:40:05 +08:00

77 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""P1-04:桌面会话探测 + 截图/全局热键能力路由(纯 stdlib,可在任何平台导入)。
契约(REPAIR_BACKLOG P1-04):
- Windows 保持现有行为(Win32 RegisterHotKey + Qt grabWindow 覆盖层)。
- Linux X11:原生全局快捷键(XGrabKey,见 x11_hotkey.py+ 原生屏幕捕获(Qt grabWindowX11 可用)。
- Linux Wayland:截图走 xdg-desktop-portal(用户授权,不绕过 compositor,见 portal_capture.py);
全局热键依赖 compositor 桌面协议(ext-global-shortcut 等),本版本无免依赖实现 →
明确告知不可用,仅保留应用内 Alt+S 快捷键,主程序其余功能不受影响。
- offscreen/无显示(unknown):能力不可用要有明确日志,主程序仍可聊天。
"""
import os
import sys
def session_kind() -> str:
"""返回 "win32" / "x11" / "wayland" / "unknown"。
判定顺序(Linux,综合 Qt 平台名与 XDG_SESSION_TYPE,见 PLATFORM_PLAN):
1. QT_QPA_PLATFORM 以 offscreen 开头 → unknown(自动化离屏,无桌面能力)
2. QT_QPA_PLATFORM 以 wayland 开头,或 WAYLAND_DISPLAY 已设置,
或 XDG_SESSION_TYPE=wayland → wayland
3. DISPLAY 已设置,或 XDG_SESSION_TYPE=x11 → x11
4. 其余(headless/无显示)→ unknown
"""
if sys.platform == "win32":
return "win32"
if sys.platform != "linux":
# 其他 POSIX(macOS 等)不在本任务支持矩阵内
return "unknown"
qt_plat = os.environ.get("QT_QPA_PLATFORM", "").strip()
if qt_plat.startswith("offscreen"):
return "unknown"
xdg_type = os.environ.get("XDG_SESSION_TYPE", "").strip().lower()
if (qt_plat.startswith("wayland")
or os.environ.get("WAYLAND_DISPLAY", "").strip()
or xdg_type == "wayland"):
return "wayland"
if os.environ.get("DISPLAY", "").strip() or xdg_type == "x11":
return "x11"
return "unknown"
def hotkey_plan(kind: str):
"""全局热键能力路由 → (thread_factory | None, message)。
thread_factory() 返回 QThread(带 triggered 信号);None 表示无全局热键能力
(调用方应保留应用内 QShortcut 兜底)。message 需要打印以明确当前能力。
"""
if kind == "win32":
from ui.views.system_tools.global_hotkey import GlobalHotkeyThread
return GlobalHotkeyThread, "[GlobalHotkey] Windows:系统级全局热键 Alt+SRegisterHotKey"
if kind == "x11":
from ui.views.system_tools.x11_hotkey import X11HotkeyThread
return X11HotkeyThread, "[GlobalHotkey] X11:原生全局热键 Alt+SXGrabKey"
if kind == "wayland":
return None, ("[GlobalHotkey] Wayland:全局热键需要 compositor 桌面协议"
"ext-global-shortcut 等),本版本未启用 → 仅提供应用内 Alt+S 快捷键"
"(窗口获焦时生效)与截图按钮;其余功能不受影响")
return None, ("[GlobalHotkey] 当前环境无显示服务(offscreen/无 DISPLAY)→ 全局热键不可用;"
"应用内 Alt+S 快捷键同样不可用,可用截图按钮以外的全部功能")
def capture_plan(kind: str):
"""截图能力路由 → (mode, message)。
mode: "overlay"Qt grabWindow 覆盖层,win32/x11
"portal"xdg-desktop-portal 交互截图,wayland
"unavailable"unknown:明确告知,主程序其余功能不受影响)
"""
if kind in ("win32", "x11"):
return "overlay", None
if kind == "wayland":
return "portal", None
return "unavailable", ("[Screenshot] 当前环境无法获取屏幕画面(offscreen/无显示)→ "
"截图功能不可用;聊天与其他功能不受影响")