子进程效率低,换成udp接收控制指令
parent
73fd35edb8
commit
d181dd4da8
|
|
@ -26,13 +26,16 @@ pyz = PYZ(a.pure)
|
|||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[('O', None, 'OPTION'), ('O', None, 'OPTION')],
|
||||
exclude_binaries=True,
|
||||
name='PCM_Viewer',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
|
|
@ -40,12 +43,3 @@ exe = EXE(
|
|||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
name='PCM_Viewer',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -117,3 +117,4 @@ Measure-Command { Start-Process -FilePath "dist\PCM_Viewer.exe" -Wait }
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -60,3 +60,4 @@ pip install -r requirements.txt
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -96,3 +96,4 @@ if __name__ == "__main__":
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
246
main.py
246
main.py
|
|
@ -12,6 +12,8 @@ from __future__ import annotations
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import threading
|
||||
from dataclasses import dataclass, asdict, field
|
||||
from typing import Optional, Dict, Any
|
||||
import math
|
||||
|
|
@ -24,10 +26,12 @@ os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "0"
|
|||
from PyQt6.QtCore import Qt, QRectF, pyqtSignal, QObject, QTimer, QPointF, QSizeF
|
||||
from PyQt6.QtWidgets import QSizePolicy
|
||||
from PyQt6.QtGui import QFontMetrics
|
||||
from PyQt6.QtGui import QBrush, QColor, QPen, QPixmap, QFont, QCursor, QShortcut, QKeySequence
|
||||
from PyQt6.QtGui import QBrush, QColor, QPen, QPixmap, QFont, QCursor, QShortcut, QKeySequence, QIcon
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QMainWindow,
|
||||
QSystemTrayIcon,
|
||||
QMenu,
|
||||
QWidget,
|
||||
QHBoxLayout,
|
||||
QVBoxLayout,
|
||||
|
|
@ -48,6 +52,7 @@ from PyQt6.QtWidgets import (
|
|||
QMessageBox,
|
||||
QPlainTextEdit,
|
||||
QListWidget,
|
||||
QAction,
|
||||
QListWidgetItem,
|
||||
QColorDialog,
|
||||
QGroupBox,
|
||||
|
|
@ -1451,12 +1456,115 @@ class InfluxConfigDialog(QDialog):
|
|||
pass
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# UDP 命令监听器
|
||||
# -----------------------------
|
||||
|
||||
class UDPCommandListener(QObject):
|
||||
"""UDP 命令监听器,接收来自 PCM_Report 的命令"""
|
||||
show_signal = pyqtSignal()
|
||||
hide_signal = pyqtSignal()
|
||||
load_layout_signal = pyqtSignal(str)
|
||||
fullscreen_signal = pyqtSignal()
|
||||
exit_signal = pyqtSignal()
|
||||
|
||||
def __init__(self, port=9876):
|
||||
super().__init__()
|
||||
self.port = port
|
||||
self.running = False
|
||||
self.socket = None
|
||||
|
||||
def start(self):
|
||||
"""在后台线程启动 UDP 监听"""
|
||||
self.running = True
|
||||
thread = threading.Thread(target=self._listen, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def _listen(self):
|
||||
"""UDP 监听循环"""
|
||||
try:
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind(('127.0.0.1', self.port))
|
||||
self.socket.settimeout(1.0)
|
||||
|
||||
print(f"[UDP] 监听端口 {self.port}...")
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
data, addr = self.socket.recvfrom(4096)
|
||||
message = data.decode('utf-8')
|
||||
print(f"[UDP] 收到命令: {message}")
|
||||
self._handle_command(message)
|
||||
except socket.timeout:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"[UDP] 处理错误: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[UDP] 启动失败: {e}")
|
||||
finally:
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
|
||||
def _handle_command(self, message: str):
|
||||
"""处理接收到的命令"""
|
||||
try:
|
||||
cmd = json.loads(message)
|
||||
action = cmd.get('action')
|
||||
|
||||
if action == 'show':
|
||||
self.show_signal.emit()
|
||||
elif action == 'hide':
|
||||
self.hide_signal.emit()
|
||||
elif action == 'load_layout':
|
||||
path = cmd.get('path', '')
|
||||
if path:
|
||||
self.load_layout_signal.emit(path)
|
||||
elif action == 'fullscreen':
|
||||
self.fullscreen_signal.emit()
|
||||
elif action == 'show_and_fullscreen':
|
||||
path = cmd.get('path', '')
|
||||
if path:
|
||||
self.load_layout_signal.emit(path)
|
||||
self.show_signal.emit()
|
||||
self.fullscreen_signal.emit()
|
||||
elif action == 'exit':
|
||||
self.exit_signal.emit()
|
||||
except json.JSONDecodeError:
|
||||
# 兼容简单文本命令
|
||||
if message == 'show':
|
||||
self.show_signal.emit()
|
||||
elif message == 'hide':
|
||||
self.hide_signal.emit()
|
||||
elif message.startswith('load:'):
|
||||
path = message[5:]
|
||||
self.load_layout_signal.emit(path)
|
||||
elif message == 'fullscreen':
|
||||
self.fullscreen_signal.emit()
|
||||
elif message == 'exit':
|
||||
self.exit_signal.emit()
|
||||
|
||||
def stop(self):
|
||||
"""停止监听"""
|
||||
self.running = False
|
||||
if self.socket:
|
||||
self.socket.close()
|
||||
|
||||
def send_response(self, addr, message: str):
|
||||
"""发送响应到指定地址"""
|
||||
try:
|
||||
self.socket.sendto(message.encode('utf-8'), addr)
|
||||
except Exception as e:
|
||||
print(f"[UDP] 发送响应失败: {e}")
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# 主窗口
|
||||
# -----------------------------
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
def __init__(self, start_hidden=False):
|
||||
super().__init__()
|
||||
self.setWindowTitle("PCM Viewer - Widgets Dashboard")
|
||||
self.resize(1400, 840)
|
||||
|
|
@ -1466,6 +1574,7 @@ class MainWindow(QMainWindow):
|
|||
# 全屏编辑标志(展示模式本身也会全屏)
|
||||
self._fullscreen_edit = False
|
||||
self._first_show = True # 用于在第一次显示时最大化并校正滚动条
|
||||
self._start_hidden = start_hidden # 启动时是否隐藏窗口
|
||||
|
||||
self.view = DashboardView()
|
||||
self.prop = PropertyPanel()
|
||||
|
|
@ -1476,6 +1585,10 @@ class MainWindow(QMainWindow):
|
|||
# 全局 Influx 客户端:延迟创建,只在需要时初始化(减少启动时间)
|
||||
self.influx_client = None
|
||||
|
||||
# 系统托盘图标
|
||||
self.tray_icon = None
|
||||
self._setup_tray_icon()
|
||||
|
||||
self.splitter = QSplitter()
|
||||
# 左侧:画布
|
||||
self.splitter.addWidget(self.view)
|
||||
|
|
@ -2051,23 +2164,144 @@ class MainWindow(QMainWindow):
|
|||
# 这里避免频繁弹窗,暂时只打印,也可以根据需要改成状态栏提示
|
||||
print("Influx error:", msg)
|
||||
|
||||
def load_layout(self, path: str):
|
||||
"""加载布局文件(代理调用 DashboardView 的 load_layout)"""
|
||||
if hasattr(self.view, 'load_layout'):
|
||||
self.view.load_layout(path)
|
||||
self._layout_path = path
|
||||
self._update_current_file_label()
|
||||
|
||||
def _setup_tray_icon(self):
|
||||
"""设置系统托盘图标"""
|
||||
if not QSystemTrayIcon.isSystemTrayAvailable():
|
||||
print("[Tray] 系统托盘不可用")
|
||||
return
|
||||
|
||||
self.tray_icon = QSystemTrayIcon(self)
|
||||
|
||||
# 使用默认图标(可以替换为自定义图标)
|
||||
icon = self.style().standardIcon(self.style().StandardPixmap.SP_ComputerIcon)
|
||||
self.tray_icon.setIcon(icon)
|
||||
|
||||
# 创建托盘菜单
|
||||
tray_menu = QMenu()
|
||||
|
||||
show_action = QAction("显示窗口", self)
|
||||
show_action.triggered.connect(self._show_window)
|
||||
tray_menu.addAction(show_action)
|
||||
|
||||
hide_action = QAction("隐藏窗口", self)
|
||||
hide_action.triggered.connect(self.hide)
|
||||
tray_menu.addAction(hide_action)
|
||||
|
||||
tray_menu.addSeparator()
|
||||
|
||||
quit_action = QAction("退出", self)
|
||||
quit_action.triggered.connect(QApplication.instance().quit)
|
||||
tray_menu.addAction(quit_action)
|
||||
|
||||
self.tray_icon.setContextMenu(tray_menu)
|
||||
|
||||
# 双击托盘图标显示窗口
|
||||
self.tray_icon.activated.connect(self._on_tray_activated)
|
||||
|
||||
# 显示托盘图标
|
||||
self.tray_icon.show()
|
||||
self.tray_icon.setToolTip("PCM Viewer")
|
||||
|
||||
def _on_tray_activated(self, reason):
|
||||
"""托盘图标被激活"""
|
||||
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
|
||||
self._show_window()
|
||||
|
||||
def _show_window(self):
|
||||
"""显示窗口"""
|
||||
self.show()
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
|
||||
def _show_and_fullscreen(self, layout_path: str = None):
|
||||
"""显示窗口并进入全屏展示模式"""
|
||||
if layout_path:
|
||||
self.load_layout(layout_path)
|
||||
self._show_window()
|
||||
# 进入展示模式
|
||||
if self._edit_mode:
|
||||
self._toggle_mode()
|
||||
# 窗口全屏
|
||||
self.showFullScreen()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""关闭事件:最小化到托盘而不是退出"""
|
||||
if self.tray_icon and self.tray_icon.isVisible():
|
||||
event.ignore()
|
||||
self.hide()
|
||||
self.tray_icon.showMessage(
|
||||
"PCM Viewer",
|
||||
"程序已最小化到系统托盘",
|
||||
QSystemTrayIcon.MessageIcon.Information,
|
||||
2000
|
||||
)
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
|
||||
def main():
|
||||
# 抑制 Windows 上 QWebEngineView 的 DirectComposition 警告
|
||||
import os
|
||||
os.environ.setdefault('QT_LOGGING_RULES', 'qt.webenginecontext.debug=false')
|
||||
|
||||
# 解析命令行参数
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='PCM Viewer - 全屏展示工具')
|
||||
parser.add_argument('layout', nargs='?', help='布局JSON文件路径')
|
||||
parser.add_argument('--fullscreen', '-f', action='store_true', help='启动时进入全屏模式')
|
||||
parser.add_argument('--hidden', '-H', action='store_true', help='启动时隐藏窗口(仅显示托盘图标)')
|
||||
parser.add_argument('--udp-port', '-p', type=int, default=9876, help='UDP监听端口(默认9876)')
|
||||
args = parser.parse_args()
|
||||
|
||||
# QWebEngineView 必须在 QApplication 创建之前导入
|
||||
# 已经在文件顶部导入,这里确保环境变量已设置
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView # 确保已导入
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setQuitOnLastWindowClosed(False) # 关闭窗口时不退出,保持托盘运行
|
||||
|
||||
# 应用全局样式
|
||||
apply_styles(app)
|
||||
|
||||
win = MainWindow()
|
||||
# 创建主窗口(根据参数决定是否启动时隐藏)
|
||||
start_hidden = args.hidden or args.fullscreen # 全屏模式也先隐藏,等待UDP命令
|
||||
win = MainWindow(start_hidden=start_hidden)
|
||||
|
||||
# 如果提供了布局文件路径,加载它
|
||||
if args.layout and os.path.exists(args.layout):
|
||||
win.load_layout(args.layout)
|
||||
|
||||
# 启动 UDP 监听器
|
||||
udp_listener = UDPCommandListener(port=args.udp_port)
|
||||
udp_listener.show_signal.connect(win._show_window)
|
||||
udp_listener.hide_signal.connect(win.hide)
|
||||
udp_listener.load_layout_signal.connect(win.load_layout)
|
||||
udp_listener.fullscreen_signal.connect(lambda: win._show_and_fullscreen())
|
||||
udp_listener.exit_signal.connect(app.quit)
|
||||
udp_listener.start()
|
||||
|
||||
# 根据参数决定是否显示窗口
|
||||
if args.fullscreen:
|
||||
# 全屏模式:显示并进入展示模式
|
||||
win._show_and_fullscreen()
|
||||
elif args.hidden:
|
||||
# 隐藏模式:不显示窗口,仅托盘图标
|
||||
print("[Main] 启动时隐藏窗口,等待UDP命令...")
|
||||
else:
|
||||
# 正常模式:显示窗口
|
||||
win.show()
|
||||
|
||||
# 应用程序退出时清理
|
||||
def on_quit():
|
||||
udp_listener.stop()
|
||||
|
||||
app.aboutToQuit.connect(on_quit)
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue