xiufubug
parent
d470f1f82c
commit
d074ac4e16
|
|
@ -26,20 +26,27 @@ 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,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
contents_directory='PCM_Viewer_lib',
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
name='PCM_Viewer',
|
||||
)
|
||||
|
|
|
|||
13
build.py
13
build.py
|
|
@ -47,6 +47,13 @@ def main():
|
|||
"--name=PCM_Viewer",
|
||||
"--windowed", # 不显示控制台窗口
|
||||
"--onefile" if not onedir else "--onedir",
|
||||
]
|
||||
|
||||
# onedir模式:将依赖文件放到专用子文件夹,避免与其他程序冲突
|
||||
if onedir:
|
||||
cmd.append("--contents-directory=PCM_Viewer_lib")
|
||||
|
||||
cmd.extend([
|
||||
# ========== 启动速度优化 ==========
|
||||
"--noupx", # 不使用 UPX 压缩(UPX 会增加启动时间)
|
||||
"--optimize=2", # Python 字节码优化级别(0-2,2 最高)
|
||||
|
|
@ -67,7 +74,7 @@ def main():
|
|||
# 只收集必要的 PyQt6 模块(不收集全部,减少体积)
|
||||
"--collect-all=PyQt6.QtWebEngineWidgets", # WebEngine 需要完整收集
|
||||
"main.py"
|
||||
]
|
||||
])
|
||||
|
||||
print("开始打包...")
|
||||
print(f"模式: {'单文件' if not onedir else '文件夹'}")
|
||||
|
|
@ -87,6 +94,10 @@ def main():
|
|||
else:
|
||||
print("文件夹位置: dist/PCM_Viewer/")
|
||||
print("可执行文件: dist/PCM_Viewer/PCM_Viewer.exe")
|
||||
print("\n注意:")
|
||||
print("1. 所有依赖文件已放入 PCM_Viewer_lib 子文件夹,避免与其他程序冲突")
|
||||
print("2. 可以将 PCM_Viewer.exe 和 PCM_Viewer_lib 文件夹一起复制到任意位置")
|
||||
print("3. 可以将多个不同的 onedir 程序放在同一目录")
|
||||
else:
|
||||
print("\n打包失败!")
|
||||
sys.exit(1)
|
||||
|
|
|
|||
92
main.py
92
main.py
|
|
@ -1473,6 +1473,21 @@ class UDPCommandListener(QObject):
|
|||
self.running = False
|
||||
self.socket = None
|
||||
|
||||
@staticmethod
|
||||
def is_port_available(port: int) -> bool:
|
||||
"""检测端口是否可用"""
|
||||
test_socket = None
|
||||
try:
|
||||
test_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
test_socket.bind(('127.0.0.1', port))
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
finally:
|
||||
if test_socket:
|
||||
test_socket.close()
|
||||
|
||||
def start(self):
|
||||
"""在后台线程启动 UDP 监听"""
|
||||
self.running = True
|
||||
|
|
@ -1485,7 +1500,7 @@ class UDPCommandListener(QObject):
|
|||
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)
|
||||
self.socket.settimeout(0.1) # 减少超时时间,加快关闭响应
|
||||
|
||||
print(f"[UDP] 监听端口 {self.port}...")
|
||||
|
||||
|
|
@ -1526,7 +1541,6 @@ class UDPCommandListener(QObject):
|
|||
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()
|
||||
|
|
@ -1563,13 +1577,14 @@ class UDPCommandListener(QObject):
|
|||
# -----------------------------
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, start_hidden=False):
|
||||
def __init__(self, start_hidden=False, enable_edit=False):
|
||||
super().__init__()
|
||||
self.setWindowTitle("PCM Viewer - Widgets Dashboard")
|
||||
self.resize(1400, 840)
|
||||
# 设置焦点策略,确保能够接收键盘事件(特别是 ESC 键)
|
||||
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
self._edit_mode = True
|
||||
self._enable_edit = enable_edit # 是否启用编辑模式
|
||||
self._edit_mode = enable_edit # 默认根据enable_edit决定
|
||||
# 全屏编辑标志(展示模式本身也会全屏)
|
||||
self._fullscreen_edit = False
|
||||
self._first_show = True # 用于在第一次显示时最大化并校正滚动条
|
||||
|
|
@ -1738,6 +1753,9 @@ class MainWindow(QMainWindow):
|
|||
if want_full:
|
||||
# 真全屏:使用 Qt 的 showFullScreen,退出时用 showMaximized 恢复普通 Windows 窗口
|
||||
self.showFullScreen()
|
||||
# 全屏后强制窗口到最上层
|
||||
self.raise_()
|
||||
self.activateWindow()
|
||||
# 全屏后确保窗口获得焦点,以便接收键盘事件(ESC 键)
|
||||
self.setFocus()
|
||||
# 全屏时关闭滚动条,避免 1920x1080 屏幕上还出现滚动条
|
||||
|
|
@ -1811,7 +1829,7 @@ class MainWindow(QMainWindow):
|
|||
self._apply_ui_mode()
|
||||
|
||||
def _apply_ui_mode(self):
|
||||
"""根据当前模式控制按钮可用性,保证“全屏编辑只拖拽/缩放”更纯粹。"""
|
||||
"""根据当前模式控制按钮可用性,保证"全屏编辑只拖拽/缩放"更纯粹。"""
|
||||
if not self._edit_mode:
|
||||
# 展示模式:禁用编辑相关,并隐藏右侧/工具栏,只保留全屏画布
|
||||
for b in (
|
||||
|
|
@ -1827,10 +1845,12 @@ class MainWindow(QMainWindow):
|
|||
self.btn_load,
|
||||
):
|
||||
b.setEnabled(False)
|
||||
self.btn_mode.setEnabled(True) # 允许退出展示
|
||||
# 右侧和工具栏隐藏,达到“画布全屏”效果(Esc 或按钮退出)
|
||||
# 如果启用了编辑模式,允许切换回编辑;否则隐藏按钮
|
||||
self.btn_mode.setEnabled(self._enable_edit)
|
||||
self.btn_mode.setVisible(self._enable_edit)
|
||||
# 右侧和工具栏隐藏,达到"画布全屏"效果(Esc 或按钮退出)
|
||||
self.right_panel.setVisible(False)
|
||||
self.toolbar_widget.setVisible(False)
|
||||
self.toolbar_widget.setVisible(self._enable_edit)
|
||||
# 全屏展示:去掉 splitter 句柄、关闭滚动条,避免白边/滚动条露出
|
||||
self.splitter.setHandleWidth(0)
|
||||
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
|
|
@ -2079,9 +2099,13 @@ class MainWindow(QMainWindow):
|
|||
def keyPressEvent(self, event):
|
||||
"""ESC 支持退出全屏展示 / 全屏编辑"""
|
||||
if event.key() == Qt.Key.Key_Escape:
|
||||
# 如果当前是展示模式:ESC 直接切回编辑模式(并退出全屏)
|
||||
# 如果当前是展示模式
|
||||
if not self._edit_mode:
|
||||
# 如果启用了编辑模式,ESC切回编辑;否则隐藏窗口
|
||||
if self._enable_edit:
|
||||
self._toggle_mode()
|
||||
else:
|
||||
self.hide()
|
||||
return
|
||||
# 如果是全屏编辑:只退出全屏编辑,保留编辑模式
|
||||
if self._fullscreen_edit:
|
||||
|
|
@ -2169,6 +2193,7 @@ class MainWindow(QMainWindow):
|
|||
self.view.load_layout(path)
|
||||
self._layout_path = path
|
||||
self._update_current_file_label()
|
||||
self._refresh_item_list() # 更新组件列表
|
||||
|
||||
def _setup_tray_icon(self):
|
||||
"""设置系统托盘图标"""
|
||||
|
|
@ -2215,6 +2240,9 @@ class MainWindow(QMainWindow):
|
|||
|
||||
def _show_window(self):
|
||||
"""显示窗口"""
|
||||
# 先恢复窗口可见性,避免从最小化直接全屏导致显示不全
|
||||
if self.isMinimized():
|
||||
self.setWindowState(Qt.WindowState.WindowNoState)
|
||||
self.show()
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
|
|
@ -2224,11 +2252,20 @@ class MainWindow(QMainWindow):
|
|||
if layout_path:
|
||||
self.load_layout(layout_path)
|
||||
self._show_window()
|
||||
# 进入展示模式
|
||||
# 确保进入展示模式(会自动处理全屏和label边框等)
|
||||
if self._edit_mode:
|
||||
self._toggle_mode()
|
||||
# 窗口全屏
|
||||
self.showFullScreen()
|
||||
else:
|
||||
# 已经是展示模式,确保所有组件锁定且边框隐藏
|
||||
for it in self.view.scene_obj.items():
|
||||
if isinstance(it, DashboardItem):
|
||||
it.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsMovable, False)
|
||||
it.setFlag(QGraphicsRectItem.GraphicsItemFlag.ItemIsSelectable, False)
|
||||
if isinstance(it, (LabelItem, ArrowItem)):
|
||||
it.set_edit_mode(False)
|
||||
self.view.set_edit_mode(False)
|
||||
self._update_window_fullscreen()
|
||||
self._apply_ui_mode()
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""关闭事件:最小化到托盘而不是退出"""
|
||||
|
|
@ -2242,6 +2279,9 @@ class MainWindow(QMainWindow):
|
|||
2000
|
||||
)
|
||||
else:
|
||||
# 真正退出时,确保清理资源
|
||||
if hasattr(self, '_udp_listener'):
|
||||
self._udp_listener.stop()
|
||||
event.accept()
|
||||
|
||||
|
||||
|
|
@ -2254,10 +2294,17 @@ def main():
|
|||
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('--show', '-s', action='store_true', help='启动时显示窗口(默认隐藏)')
|
||||
parser.add_argument('--edit', '-e', action='store_true', help='启用编辑模式(默认仅支持展示)')
|
||||
parser.add_argument('--udp-port', '-p', type=int, default=9876, help='UDP监听端口(默认9876)')
|
||||
args = parser.parse_args()
|
||||
|
||||
# 检测 UDP 端口是否可用
|
||||
if not UDPCommandListener.is_port_available(args.udp_port):
|
||||
print(f"[错误] UDP 端口 {args.udp_port} 已被占用,程序可能已在运行")
|
||||
print(f"[错误] 请检查是否有其他实例正在运行,或使用 --udp-port 指定其他端口")
|
||||
sys.exit(1)
|
||||
|
||||
# QWebEngineView 必须在 QApplication 创建之前导入
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
|
||||
|
|
@ -2267,9 +2314,9 @@ def main():
|
|||
# 应用全局样式
|
||||
apply_styles(app)
|
||||
|
||||
# 创建主窗口(根据参数决定是否启动时隐藏)
|
||||
start_hidden = args.hidden or args.fullscreen # 全屏模式也先隐藏,等待UDP命令
|
||||
win = MainWindow(start_hidden=start_hidden)
|
||||
# 创建主窗口(默认隐藏,除非指定 --show 或 --edit)
|
||||
start_hidden = not (args.show or args.edit) # --edit 或 --show 时显示窗口
|
||||
win = MainWindow(start_hidden=start_hidden, enable_edit=args.edit)
|
||||
|
||||
# 如果提供了布局文件路径,加载它
|
||||
if args.layout and os.path.exists(args.layout):
|
||||
|
|
@ -2284,16 +2331,19 @@ def main():
|
|||
udp_listener.exit_signal.connect(app.quit)
|
||||
udp_listener.start()
|
||||
|
||||
# 保存到窗口对象,以便在关闭时清理
|
||||
win._udp_listener = udp_listener
|
||||
|
||||
# 根据参数决定是否显示窗口
|
||||
if args.fullscreen:
|
||||
# 全屏模式:显示并进入展示模式
|
||||
win._show_and_fullscreen()
|
||||
elif args.hidden:
|
||||
# 隐藏模式:不显示窗口,仅托盘图标
|
||||
print("[Main] 启动时隐藏窗口,等待UDP命令...")
|
||||
else:
|
||||
# 正常模式:显示窗口
|
||||
elif args.show:
|
||||
# 显示模式:显示窗口
|
||||
win.show()
|
||||
else:
|
||||
# 默认隐藏模式:不显示窗口,仅托盘图标
|
||||
print("[Main] 启动时隐藏窗口,等待UDP命令...")
|
||||
|
||||
# 应用程序退出时清理
|
||||
def on_quit():
|
||||
|
|
|
|||
Loading…
Reference in New Issue