解决windows无法嵌入web面板问题
This commit is contained in:
parent
812e5863cb
commit
9e45efe306
@ -1,5 +1,4 @@
|
||||
|
||||
"""
|
||||
"""
|
||||
主窗口设置模块
|
||||
|
||||
负责主窗口的界面构建和事件绑定:
|
||||
@ -7,22 +6,259 @@
|
||||
- 停靠窗口设置
|
||||
- 事件连接和信号处理
|
||||
"""
|
||||
import os
|
||||
WEB_BROWSER_PROCESS_CODE = r"""
|
||||
import sys
|
||||
import os
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
|
||||
from PyQt5.QtCore import Qt, QUrl, QTimer
|
||||
from PyQt5.QtGui import QPalette, QColor
|
||||
|
||||
from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor
|
||||
# 禁用硬件加速
|
||||
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer --disable-gpu-compositing --in-process-gpu"
|
||||
os.environ["QT_WEBENGINE_DISABLE_GPU"] = "1"
|
||||
os.environ["QT_OPENGL"] = "software"
|
||||
|
||||
# QtWebEngine 导入(环境变量已在 main.py 中配置)
|
||||
try:
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
|
||||
WEB_ENGINE_AVAILABLE = True
|
||||
print("✓ QtWebEngineWidgets 已导入")
|
||||
except ImportError:
|
||||
WEB_ENGINE_AVAILABLE = False
|
||||
QWebEngineView = None
|
||||
print("⚠️ QtWebEngine 不可用")
|
||||
|
||||
|
||||
class EmbeddedWebBrowser(QWidget):
|
||||
|
||||
def __init__(self, parent=None, url="https://www.baidu.com", parent_window_id=0):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Web 浏览器")
|
||||
self.parent_window_id = parent_window_id
|
||||
self.url = url
|
||||
|
||||
# 设置窗口标志 - 如果要嵌入,使用子窗口样式
|
||||
if parent_window_id > 0:
|
||||
# 使用 FramelessWindowHint 去除边框,便于嵌入
|
||||
self.setWindowFlags(Qt.FramelessWindowHint)
|
||||
else:
|
||||
self.setWindowFlags(Qt.Window)
|
||||
|
||||
self.resize(800, 600)
|
||||
|
||||
# 设置背景色
|
||||
palette = self.palette()
|
||||
palette.setColor(QPalette.Window, QColor(25, 25, 27))
|
||||
self.setPalette(palette)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
# 创建布局
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
if WEB_ENGINE_AVAILABLE:
|
||||
# 创建 WebEngine 视图
|
||||
self.web_view = QWebEngineView()
|
||||
|
||||
# 设置样式
|
||||
self.web_view.setStyleSheet("background-color: #19191b;")
|
||||
|
||||
# 禁用硬件加速
|
||||
settings = self.web_view.settings()
|
||||
settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, False)
|
||||
settings.setAttribute(QWebEngineSettings.WebGLEnabled, False)
|
||||
settings.setAttribute(QWebEngineSettings.PluginsEnabled, False)
|
||||
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
|
||||
settings.setAttribute(QWebEngineSettings.AutoLoadImages, True)
|
||||
settings.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
|
||||
|
||||
# 连接加载信号
|
||||
self.web_view.loadStarted.connect(self.on_load_started)
|
||||
self.web_view.loadProgress.connect(self.on_load_progress)
|
||||
self.web_view.loadFinished.connect(self.on_load_finished)
|
||||
|
||||
layout.addWidget(self.web_view)
|
||||
print("[WebEngine] WebEngine 视图已创建")
|
||||
|
||||
# 延迟加载网页,确保窗口完全初始化
|
||||
QTimer.singleShot(500, lambda: self.load_url(url))
|
||||
else:
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
label = QLabel("QtWebEngine 不可用\n请安装: pip install PyQtWebEngine")
|
||||
label.setAlignment(Qt.AlignCenter)
|
||||
label.setStyleSheet("color: white; font-size: 14px;")
|
||||
layout.addWidget(label)
|
||||
|
||||
def on_load_started(self):
|
||||
print("[WebEngine] 开始加载网页...")
|
||||
|
||||
def on_load_progress(self, progress):
|
||||
if progress % 20 == 0: # 每20%打印一次
|
||||
print(f"[WebEngine] 加载进度: {progress}%")
|
||||
|
||||
def on_load_finished(self, success):
|
||||
if success:
|
||||
print("[WebEngine] 网页加载成功")
|
||||
else:
|
||||
print("[WebEngine] 网页加载失败")
|
||||
|
||||
def load_url(self, url):
|
||||
if WEB_ENGINE_AVAILABLE and hasattr(self, 'web_view'):
|
||||
print(f"[WebEngine] 准备加载 URL: {url}")
|
||||
qurl = QUrl(url)
|
||||
print(f"[WebEngine] QUrl 有效性: {qurl.isValid()}")
|
||||
print(f"[WebEngine] QUrl 内容: {qurl.toString()}")
|
||||
self.web_view.load(qurl)
|
||||
print(f"[WebEngine] load() 方法已调用")
|
||||
else:
|
||||
print(f"[WebEngine] 无法加载 URL - WEB_ENGINE_AVAILABLE={WEB_ENGINE_AVAILABLE}, has_web_view={hasattr(self, 'web_view')}")
|
||||
|
||||
def get_native_window_id(self):
|
||||
|
||||
return int(self.winId())
|
||||
|
||||
def embed_into_parent(self):
|
||||
|
||||
if self.parent_window_id > 0:
|
||||
try:
|
||||
print(f"[WebEngine] 开始嵌入窗口到父窗口 ID: {self.parent_window_id}")
|
||||
|
||||
# Windows 平台使用 win32 API 嵌入窗口
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
# 获取窗口句柄
|
||||
hwnd = int(self.winId())
|
||||
parent_hwnd = self.parent_window_id
|
||||
|
||||
print(f"[WebEngine] 子窗口句柄: {hwnd}")
|
||||
print(f"[WebEngine] 父窗口句柄: {parent_hwnd}")
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
|
||||
# 先获取父窗口大小
|
||||
rect = wintypes.RECT()
|
||||
user32.GetClientRect(parent_hwnd, ctypes.byref(rect))
|
||||
width = rect.right - rect.left
|
||||
height = rect.bottom - rect.top
|
||||
print(f"[WebEngine] 父窗口大小: {width}x{height}")
|
||||
|
||||
# 设置父窗口
|
||||
result = user32.SetParent(hwnd, parent_hwnd)
|
||||
print(f"[WebEngine] SetParent 结果: {result}")
|
||||
|
||||
# 设置窗口样式为子窗口
|
||||
GWL_STYLE = -16
|
||||
WS_CHILD = 0x40000000
|
||||
WS_VISIBLE = 0x10000000
|
||||
WS_CLIPCHILDREN = 0x02000000
|
||||
WS_CLIPSIBLINGS = 0x04000000
|
||||
|
||||
old_style = user32.GetWindowLongW(hwnd, GWL_STYLE)
|
||||
print(f"[WebEngine] 旧窗口样式: {hex(old_style)}")
|
||||
|
||||
# 设置为子窗口样式,移除边框和标题栏
|
||||
new_style = (WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS)
|
||||
user32.SetWindowLongW(hwnd, GWL_STYLE, new_style)
|
||||
|
||||
print(f"[WebEngine] 新窗口样式: {hex(new_style)}")
|
||||
|
||||
# 调整窗口位置和大小,填充整个父窗口
|
||||
SWP_FRAMECHANGED = 0x0020
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
SWP_NOZORDER = 0x0004
|
||||
|
||||
result = user32.SetWindowPos(
|
||||
hwnd,
|
||||
0, # HWND_TOP
|
||||
0, 0, # x, y
|
||||
width, height, # width, height
|
||||
SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOZORDER
|
||||
)
|
||||
print(f"[WebEngine] SetWindowPos 结果: {result}")
|
||||
|
||||
# 强制刷新窗口
|
||||
user32.UpdateWindow(hwnd)
|
||||
user32.InvalidateRect(hwnd, None, True)
|
||||
|
||||
print(f"[WebEngine] 已成功嵌入到父窗口 (Windows)")
|
||||
return True
|
||||
else:
|
||||
# 其他平台使用 Qt 的方式
|
||||
from PyQt5.QtGui import QWindow
|
||||
parent_window = QWindow.fromWinId(self.parent_window_id)
|
||||
if parent_window:
|
||||
self.windowHandle().setParent(parent_window)
|
||||
print(f"[WebEngine] 已嵌入到父窗口 (Qt): {self.parent_window_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[WebEngine] 嵌入窗口失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# 设置标准输出编码为 UTF-8,避免 Windows GBK 编码问题
|
||||
if sys.platform == 'win32':
|
||||
import io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
print("=" * 60)
|
||||
print("[WebEngine] 启动独立 WebEngine 进程")
|
||||
print("=" * 60)
|
||||
|
||||
# 创建应用
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# 从命令行参数获取 URL 和窗口 ID
|
||||
url = sys.argv[1] if len(sys.argv) > 1 else "https://www.baidu.com"
|
||||
parent_window_id = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||
|
||||
print(f"[WebEngine] 参数:")
|
||||
print(f" - URL: {url}")
|
||||
print(f" - 父窗口 ID: {parent_window_id}")
|
||||
|
||||
# 创建浏览器窗口
|
||||
browser = EmbeddedWebBrowser(url=url, parent_window_id=parent_window_id)
|
||||
|
||||
# 显示窗口
|
||||
browser.show()
|
||||
|
||||
# 输出窗口 ID 供父进程使用
|
||||
window_id = browser.get_native_window_id()
|
||||
print(f"WINDOW_ID:{window_id}")
|
||||
sys.stdout.flush()
|
||||
|
||||
# 等待网页开始加载后再嵌入窗口
|
||||
if parent_window_id > 0:
|
||||
# 延迟2秒,确保网页已经开始加载和渲染
|
||||
QTimer.singleShot(10, browser.embed_into_parent)
|
||||
|
||||
print("[WebEngine] 进程已就绪")
|
||||
print("=" * 60)
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# ==================== 禁用 QtWebEngine 硬件加速 ====================
|
||||
# 必须在导入 QtWebEngine 之前设置环境变量
|
||||
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer --disable-gpu-compositing --in-process-gpu"
|
||||
os.environ["QT_WEBENGINE_DISABLE_GPU"] = "1"
|
||||
os.environ["QT_OPENGL"] = "software"
|
||||
print("✓ QtWebEngine 硬件加速已禁用,使用软件渲染模式")
|
||||
# ==================== 结束配置 ====================
|
||||
|
||||
from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor
|
||||
# from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction,
|
||||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
||||
@ -39,7 +275,7 @@ from ui.widgets import (CustomMeta3DWidget, CustomFileView, CustomTreeWidget,
|
||||
UniversalMessageDialog)
|
||||
from ui.icon_manager import get_icon_manager, get_icon
|
||||
|
||||
import yaml
|
||||
# import yaml
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QListWidget, QPushButton, QDialogButtonBox, QCheckBox, QLabel, QHBoxLayout
|
||||
from PyQt5.QtCore import Qt
|
||||
|
||||
@ -1516,13 +1752,13 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.createSamplePanelAction.triggered.connect(self.world.info_panel_manager.onCreateSampleInfoPanel)
|
||||
self.create3DSamplePanelAction.triggered.connect(self.onCreate3DSampleInfoPanel)
|
||||
#self.webBrowserAction.triggered.connect(self.openWebBrowser_win)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
self.webBrowserAction.triggered.connect(self.openWebBrowser_win)
|
||||
else:
|
||||
self.webBrowserAction.triggered.connect(self.openWebBrowser)
|
||||
|
||||
|
||||
def getCreateMenuActions(self):
|
||||
"""获取所有创建菜单动作的字典 - 供右键菜单使用"""
|
||||
return {
|
||||
@ -2307,90 +2543,90 @@ class MainWindow(QMainWindow):
|
||||
self.bottomDock.setWidget(bottomDockContainer)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock)
|
||||
|
||||
# # 创建底部停靠控制台
|
||||
# self.consoleDock = QDockWidget("控制台", self)
|
||||
# self.consoleDock.setStyleSheet("""
|
||||
# QDockWidget {
|
||||
# background-color: #19191b;
|
||||
# color: #ffffff;
|
||||
# border: none;
|
||||
# }
|
||||
# QDockWidget::title {
|
||||
# background-color: #19191b;
|
||||
# padding: 8px 10px;
|
||||
# border-bottom: none;
|
||||
# text-align: left;
|
||||
# font-family: 'Microsoft YaHei', 'Inter', sans-serif;
|
||||
# font-size: 14px;
|
||||
# font-weight: 500;
|
||||
# }
|
||||
# QDockWidget::close-button {
|
||||
# background-color: rgba(89, 100, 113, 0.3);
|
||||
# border: 1px solid rgba(184, 211, 241, 0.2);
|
||||
# icon-size: 10px;
|
||||
# border-radius: 2px;
|
||||
# width: 16px;
|
||||
# height: 16px;
|
||||
# right: 6px;
|
||||
# top: 6px;
|
||||
# }
|
||||
# QDockWidget::float-button {
|
||||
# background-color: rgba(89, 100, 113, 0.3);
|
||||
# border: 1px solid rgba(184, 211, 241, 0.2);
|
||||
# icon-size: 10px;
|
||||
# border-radius: 2px;
|
||||
# width: 16px;
|
||||
# height: 16px;
|
||||
# right: 26px;
|
||||
# top: 6px;
|
||||
# }
|
||||
# QDockWidget::close-button:hover, QDockWidget::float-button:hover {
|
||||
# background-color: rgba(89, 100, 113, 0.5);
|
||||
# border: 1px solid rgba(184, 211, 241, 0.3);
|
||||
# }
|
||||
# QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
|
||||
# background-color: #3067c0;
|
||||
# border: 1px solid rgba(48, 103, 192, 0.6);
|
||||
# }
|
||||
# """)
|
||||
# self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# # 为控制台添加样式
|
||||
# self.consoleView.setStyleSheet("""
|
||||
# QTextEdit {
|
||||
# background-color: #19191b;
|
||||
# color: #ebebeb;
|
||||
# border: none;
|
||||
# font-family: 'Consolas', 'Monaco', 'Microsoft YaHei', monospace;
|
||||
# font-size: 10px;
|
||||
# font-weight: 300;
|
||||
# }
|
||||
# """)
|
||||
#
|
||||
# # 创建包装容器,添加标题下方的分隔线
|
||||
# consoleDockContainer = QWidget()
|
||||
# consoleDockContainer.setStyleSheet("QWidget { background-color: #19191b; }")
|
||||
# consoleDockLayout = QVBoxLayout(consoleDockContainer)
|
||||
# consoleDockLayout.setContentsMargins(0, 0, 0, 0)
|
||||
# consoleDockLayout.setSpacing(0)
|
||||
#
|
||||
# # 添加带左右间距的分隔线
|
||||
# consoleSeparator = QFrame()
|
||||
# consoleSeparator.setFrameShape(QFrame.HLine)
|
||||
# consoleSeparator.setFrameShadow(QFrame.Plain)
|
||||
# consoleSeparator.setStyleSheet("""
|
||||
# QFrame {
|
||||
# background-color: rgba(77, 116, 189, 0.4);
|
||||
# max-height: 1px;
|
||||
# margin-left: 8px;
|
||||
# margin-right: 8px;
|
||||
# border: none;
|
||||
# }
|
||||
# """)
|
||||
# consoleDockLayout.addWidget(consoleSeparator)
|
||||
# consoleDockLayout.addWidget(self.consoleView)
|
||||
#
|
||||
# self.consoleDock.setWidget(consoleDockContainer)
|
||||
# self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
# 创建底部停靠控制台
|
||||
self.consoleDock = QDockWidget("控制台", self)
|
||||
self.consoleDock.setStyleSheet("""
|
||||
QDockWidget {
|
||||
background-color: #19191b;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
}
|
||||
QDockWidget::title {
|
||||
background-color: #19191b;
|
||||
padding: 8px 10px;
|
||||
border-bottom: none;
|
||||
text-align: left;
|
||||
font-family: 'Microsoft YaHei', 'Inter', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
QDockWidget::close-button {
|
||||
background-color: rgba(89, 100, 113, 0.3);
|
||||
border: 1px solid rgba(184, 211, 241, 0.2);
|
||||
icon-size: 10px;
|
||||
border-radius: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
}
|
||||
QDockWidget::float-button {
|
||||
background-color: rgba(89, 100, 113, 0.3);
|
||||
border: 1px solid rgba(184, 211, 241, 0.2);
|
||||
icon-size: 10px;
|
||||
border-radius: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
right: 26px;
|
||||
top: 6px;
|
||||
}
|
||||
QDockWidget::close-button:hover, QDockWidget::float-button:hover {
|
||||
background-color: rgba(89, 100, 113, 0.5);
|
||||
border: 1px solid rgba(184, 211, 241, 0.3);
|
||||
}
|
||||
QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
|
||||
background-color: #3067c0;
|
||||
border: 1px solid rgba(48, 103, 192, 0.6);
|
||||
}
|
||||
""")
|
||||
self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# 为控制台添加样式
|
||||
self.consoleView.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #19191b;
|
||||
color: #ebebeb;
|
||||
border: none;
|
||||
font-family: 'Consolas', 'Monaco', 'Microsoft YaHei', monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 300;
|
||||
}
|
||||
""")
|
||||
|
||||
# 创建包装容器,添加标题下方的分隔线
|
||||
consoleDockContainer = QWidget()
|
||||
consoleDockContainer.setStyleSheet("QWidget { background-color: #19191b; }")
|
||||
consoleDockLayout = QVBoxLayout(consoleDockContainer)
|
||||
consoleDockLayout.setContentsMargins(0, 0, 0, 0)
|
||||
consoleDockLayout.setSpacing(0)
|
||||
|
||||
# 添加带左右间距的分隔线
|
||||
consoleSeparator = QFrame()
|
||||
consoleSeparator.setFrameShape(QFrame.HLine)
|
||||
consoleSeparator.setFrameShadow(QFrame.Plain)
|
||||
consoleSeparator.setStyleSheet("""
|
||||
QFrame {
|
||||
background-color: rgba(77, 116, 189, 0.4);
|
||||
max-height: 1px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
border: none;
|
||||
}
|
||||
""")
|
||||
consoleDockLayout.addWidget(consoleSeparator)
|
||||
consoleDockLayout.addWidget(self.consoleView)
|
||||
|
||||
self.consoleDock.setWidget(consoleDockContainer)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
|
||||
# 配置Dock区域的角落归属
|
||||
# 使右侧和下侧的Dock面板可以占据右下角区域
|
||||
@ -2414,7 +2650,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 确保其他面板也正常显示
|
||||
self.bottomDock.raise_()
|
||||
#self.consoleDock.raise_()
|
||||
self.consoleDock.raise_()
|
||||
self.leftDock.raise_()
|
||||
# =========================================================================
|
||||
# ↓↓↓ 为停靠窗口的标签栏(QTabBar)设置统一样式(根据Figma设计)↓↓↓
|
||||
@ -3929,70 +4165,181 @@ class MainWindow(QMainWindow):
|
||||
self.world.setCurrentTool(None)
|
||||
print("工具栏: 取消选择工具")
|
||||
|
||||
# def openWebBrowser_win(self):
|
||||
# """
|
||||
# 使用 QMainWindow 窗口显示网页
|
||||
# 创建独立的 Web 浏览器窗口
|
||||
# """
|
||||
# from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QWidget
|
||||
# from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
# from PyQt5.QtCore import QUrl
|
||||
#
|
||||
# # 创建独立的浏览器窗口
|
||||
# window = QMainWindow(self)
|
||||
# window.setWindowTitle("Web浏览器")
|
||||
# window.setGeometry(100, 100, 1024, 768)
|
||||
#
|
||||
# # 创建中央部件和布局
|
||||
# central_widget = QWidget()
|
||||
# window.setCentralWidget(central_widget)
|
||||
# layout = QVBoxLayout(central_widget)
|
||||
#
|
||||
# # 创建 WebEngine 视图
|
||||
# web_view = QWebEngineView()
|
||||
# web_view.load(QUrl("https://www.baidu.com"))
|
||||
#
|
||||
# # 添加到布局
|
||||
# layout.addWidget(web_view)
|
||||
#
|
||||
# # 显示窗口
|
||||
# window.show()
|
||||
|
||||
def openWebBrowser_win(self):
|
||||
"""
|
||||
在独立进程中运行Web浏览器(无终端窗口)
|
||||
避免与Panda3D的OpenGL上下文冲突
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 获取当前脚本目录
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
browser_script = os.path.join(current_dir, '..', 'standalone_web_browser.py')
|
||||
|
||||
# 在独立进程中启动Web浏览器,不创建终端窗口
|
||||
"""打开Web浏览器面板(使用独立进程避免与Panda3D冲突)"""
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
subprocess.Popen([sys.executable, browser_script],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW)
|
||||
else:
|
||||
subprocess.Popen([sys.executable, browser_script])
|
||||
print("✓ 独立Web浏览器已在后台启动")
|
||||
except Exception as e:
|
||||
print(f"✗ 启动独立Web浏览器失败: {e}")
|
||||
# 如果启动失败,则使用系统默认浏览器作为备选方案
|
||||
import webbrowser
|
||||
webbrowser.open("https://www.baidu.com")
|
||||
from PyQt5.QtWidgets import QDockWidget, QWidget, QVBoxLayout
|
||||
from PyQt5.QtCore import QProcess, Qt
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
QWebEngineSettings = None
|
||||
print("⚠ QtWebEngineWidgets 未找到,Web 浏览器功能将不可用")
|
||||
main_window = self.world.main_window
|
||||
|
||||
# 尝试获取主窗口引用
|
||||
if main_window is None:
|
||||
print("🔍 尝试获取主窗口引用...")
|
||||
if hasattr(self.world, 'interface_manager'):
|
||||
print(f" - interface_manager 存在: {self.world.interface_manager}")
|
||||
if hasattr(self.world.interface_manager, 'main_window'):
|
||||
main_window = self.world.interface_manager.main_window
|
||||
print(f" - interface_manager.main_window: {main_window}")
|
||||
|
||||
if main_window is None and hasattr(self.world, 'main_window'):
|
||||
main_window = self.world.main_window
|
||||
print(f" - world.main_window: {main_window}")
|
||||
|
||||
if main_window is None and self.world.treeWidget:
|
||||
try:
|
||||
main_window = self.world.treeWidget.window()
|
||||
print(f" - 从 treeWidget 获取窗口: {main_window}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if main_window is None:
|
||||
print("✗ 无法获取主窗口引用")
|
||||
UniversalMessageDialog.show_warning(
|
||||
self, "错误", "无法获取主窗口引用", False, "确定"
|
||||
)
|
||||
return None
|
||||
else:
|
||||
print(f"✅ 使用传入的主窗口引用: {main_window}")
|
||||
|
||||
# 检查主窗口是否有效
|
||||
if not hasattr(main_window, 'addDockWidget'):
|
||||
print(f"✗ 主窗口引用无效,缺少 addDockWidget 方法")
|
||||
return None
|
||||
|
||||
# 检查是否已经存在浏览器视图
|
||||
for element in self.world.gui_elements:
|
||||
if hasattr(element, 'objectName') and element.objectName() == "WebBrowserView":
|
||||
print("⚠ 浏览器视图已经存在")
|
||||
element.show()
|
||||
element.raise_()
|
||||
return element
|
||||
|
||||
print(f"🔧 创建浏览器停靠窗口(独立进程模式),父窗口: {main_window}")
|
||||
|
||||
# 创建停靠窗口
|
||||
browser_dock = QDockWidget("信息面板", main_window)
|
||||
browser_dock.setObjectName("WebBrowserView")
|
||||
|
||||
# 创建容器 Widget
|
||||
container_widget = QWidget()
|
||||
container_layout = QVBoxLayout(container_widget)
|
||||
container_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# 创建一个占位 Widget,用于嵌入外部进程窗口
|
||||
embed_widget = QWidget()
|
||||
embed_widget.setMinimumSize(400, 300)
|
||||
container_layout.addWidget(embed_widget)
|
||||
|
||||
browser_dock.setWidget(container_widget)
|
||||
|
||||
# 添加到主窗口
|
||||
print("📍 将浏览器视图添加到主窗口")
|
||||
main_window.addDockWidget(Qt.RightDockWidgetArea, browser_dock)
|
||||
|
||||
# 获取嵌入窗口的 ID
|
||||
embed_window_id = int(embed_widget.winId())
|
||||
print(f"🔑 嵌入窗口 ID: {embed_window_id}")
|
||||
|
||||
# 启动独立进程
|
||||
print("🚀 启动独立 WebEngine 进程...")
|
||||
#web_process_script = os.path.join(os.path.dirname(__file__), "web_browser_process.py")
|
||||
import tempfile
|
||||
|
||||
temp_dir = tempfile.gettempdir()
|
||||
web_process_script = os.path.join(temp_dir, "web_browser_process_temp.py")
|
||||
|
||||
with open(web_process_script, "w", encoding="utf-8") as f:
|
||||
f.write(WEB_BROWSER_PROCESS_CODE)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 创建 QProcess(注意:必须在 start 之前)
|
||||
# -----------------------------------------------------
|
||||
# -----------------------------------------------------
|
||||
# 创建 QProcess(注意:必须在 start 之前)
|
||||
# -----------------------------------------------------
|
||||
process = QProcess(browser_dock)
|
||||
browser_dock.web_process = process # 保存进程引用
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 输出信号
|
||||
# -----------------------------------------------------
|
||||
def on_process_output():
|
||||
output = bytes(process.readAllStandardOutput()).decode('utf-8', errors='ignore')
|
||||
if output.strip():
|
||||
print(f"[WebProcess] {output.strip()}")
|
||||
|
||||
def on_process_error():
|
||||
error = bytes(process.readAllStandardError()).decode('utf-8', errors='ignore')
|
||||
if error.strip():
|
||||
print(f"[WebProcess Error] {error.strip()}")
|
||||
|
||||
def on_process_finished(exit_code, exit_status):
|
||||
print(f"⚠️ WebEngine 进程退出: {exit_code} {exit_status}")
|
||||
|
||||
process.readyReadStandardOutput.connect(on_process_output)
|
||||
process.readyReadStandardError.connect(on_process_error)
|
||||
process.finished.connect(on_process_finished)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 写入临时脚本
|
||||
# -----------------------------------------------------
|
||||
import tempfile
|
||||
temp_dir = tempfile.gettempdir()
|
||||
web_process_script = os.path.join(temp_dir, "web_browser_process_temp.py")
|
||||
|
||||
with open(web_process_script, "w", encoding="utf-8") as f:
|
||||
f.write(WEB_BROWSER_PROCESS_CODE)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# 启动子进程(这里顺序不能错)
|
||||
# -----------------------------------------------------
|
||||
python_exe = sys.executable
|
||||
url = "https://baidu.com" # 更可控的测试站点
|
||||
|
||||
print(f"🚀 启动: {python_exe} {web_process_script} {url} {embed_window_id}")
|
||||
process.start(python_exe, [web_process_script, url, str(embed_window_id)])
|
||||
|
||||
if not process.waitForStarted(3000):
|
||||
print("✗ 启动 WebEngine 失败")
|
||||
return None
|
||||
|
||||
print("✓ WebEngine 进程已启动")
|
||||
|
||||
# 添加到GUI元素列表以便管理
|
||||
self.world.gui_elements.append(browser_dock)
|
||||
|
||||
# 添加清理函数
|
||||
def cleanup_process():
|
||||
if hasattr(browser_dock, 'web_process') and browser_dock.web_process:
|
||||
print("🧹 清理 WebEngine 进程...")
|
||||
browser_dock.web_process.terminate()
|
||||
if not browser_dock.web_process.waitForFinished(2000):
|
||||
browser_dock.web_process.kill()
|
||||
|
||||
browser_dock.destroyed.connect(cleanup_process)
|
||||
|
||||
print("✓ 网页浏览器视图已创建并集成到项目中(独立进程模式)")
|
||||
return browser_dock
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 创建浏览器视图失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
UniversalMessageDialog.show_error(
|
||||
self, "错误",
|
||||
f"创建浏览器视图失败:\n{str(e)}",
|
||||
False, "确定"
|
||||
)
|
||||
return None
|
||||
|
||||
def openWebBrowser(self):
|
||||
if not WEB_ENGINE_AVAILABLE:
|
||||
return None
|
||||
try:
|
||||
#from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
# from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt5.QtWidgets import QDockWidget
|
||||
from PyQt5.QtCore import QUrl
|
||||
import os
|
||||
@ -6348,7 +6695,17 @@ def setup_main_window(world,path = None):
|
||||
QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
|
||||
if sys.platform == 'win32':
|
||||
QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL)
|
||||
|
||||
# 添加 Chromium 参数禁用硬件加速,避免与 Panda3D 的 OpenGL 冲突
|
||||
sys.argv.extend([
|
||||
'--disable-gpu',
|
||||
'--disable-software-rasterizer',
|
||||
'--disable-gpu-compositing',
|
||||
'--disable-accelerated-2d-canvas',
|
||||
'--num-raster-threads=1'
|
||||
])
|
||||
app = QApplication(sys.argv)
|
||||
print("✓ QApplication 已创建,QtWebEngine 硬件加速已禁用")
|
||||
|
||||
main_window = MainWindow(world)
|
||||
main_window.show()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user