#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ P2P Chat Client GUI 启动脚本 用法: python run_client_gui.py [--server HOST] [--port PORT] 示例: python run_client_gui.py python run_client_gui.py --server 192.168.1.100 --port 8888 """ import asyncio import argparse import logging import sys import os from typing import Optional, List # 确保能找到模块 sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from PyQt6.QtWidgets import QApplication, QMessageBox, QSplashScreen from PyQt6.QtCore import Qt, QTimer, QThread, pyqtSignal, QObject from PyQt6.QtGui import QPixmap, QFont from client.app import P2PClientApp from client.connection_manager import ConnectionState from client.ui import ( MainWindow, LoginDialog, ChatWidget, ContactListWidget, FileTransferWidget, MediaPlayerWidget, VoiceCallWidget, SystemTrayManager ) from shared.models import UserInfo, UserStatus, Message, MessageType from config import load_client_config, ClientConfig # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(sys.stdout), logging.FileHandler('client_gui.log', encoding='utf-8') ] ) logger = logging.getLogger(__name__) class AsyncWorker(QThread): """异步工作线程,用于运行 asyncio 事件循环""" connected = pyqtSignal(bool, str) # (success, message) disconnected = pyqtSignal(str) # reason message_received = pyqtSignal(object) # Message user_list_updated = pyqtSignal(list) # List[UserInfo] state_changed = pyqtSignal(str, str) # (state, reason) def __init__(self, client: P2PClientApp, user_info: UserInfo): super().__init__() self.client = client self.user_info = user_info self._running = False self._loop: Optional[asyncio.AbstractEventLoop] = None def run(self): """运行异步事件循环""" self._loop = asyncio.new_event_loop() asyncio.set_event_loop(self._loop) self._running = True try: # 连接到服务器 success = self._loop.run_until_complete( self.client.start(self.user_info) ) if success: self.connected.emit(True, "连接成功") # 设置回调 self.client.add_message_callback(self._on_message) self.client.add_state_callback(self._on_state_change) self.client.add_user_list_callback(self._on_user_list) # 保持运行 while self._running: self._loop.run_until_complete(asyncio.sleep(0.1)) else: self.connected.emit(False, "连接失败") except Exception as e: logger.error(f"Worker error: {e}") self.connected.emit(False, str(e)) finally: self._loop.close() def stop(self): """停止工作线程""" self._running = False if self._loop and self._loop.is_running(): self._loop.call_soon_threadsafe(self._loop.stop) def _on_message(self, message: Message): """消息回调""" self.message_received.emit(message) def _on_state_change(self, state: ConnectionState, reason: Optional[str]): """状态变化回调""" self.state_changed.emit(state.value, reason or "") if state == ConnectionState.DISCONNECTED: self.disconnected.emit(reason or "连接断开") def _on_user_list(self, users: List[UserInfo]): """用户列表更新回调""" self.user_list_updated.emit(users) def send_message(self, peer_id: str, content: str) -> bool: """发送消息(线程安全)""" if self._loop and self._running: future = asyncio.run_coroutine_threadsafe( self.client.send_text_message(peer_id, content), self._loop ) try: return future.result(timeout=5.0) except Exception as e: logger.error(f"Send message error: {e}") return False return False def send_file(self, peer_id: str, file_path: str) -> bool: """发送文件(线程安全)""" if self._loop and self._running: future = asyncio.run_coroutine_threadsafe( self.client.send_file(peer_id, file_path), self._loop ) try: return future.result(timeout=300.0) # 5分钟超时 except Exception as e: logger.error(f"Send file error: {e}") return False return False def send_image(self, peer_id: str, image_path: str) -> bool: """发送图片(线程安全)""" if self._loop and self._running: future = asyncio.run_coroutine_threadsafe( self.client.send_image(peer_id, image_path), self._loop ) try: return future.result(timeout=300.0) except Exception as e: logger.error(f"Send image error: {e}") return False return False def start_voice_call(self, peer_id: str) -> bool: """发起语音通话(线程安全)""" if self._loop and self._running: future = asyncio.run_coroutine_threadsafe( self.client.start_voice_call(peer_id), self._loop ) try: return future.result(timeout=30.0) except Exception as e: logger.error(f"Start voice call error: {e}") return False return False def end_voice_call(self): """结束语音通话(线程安全)""" if self._loop and self._running: self.client.end_voice_call() def accept_voice_call(self, peer_id: str) -> bool: """接听语音通话(线程安全)""" if self._loop and self._running: future = asyncio.run_coroutine_threadsafe( self.client.accept_voice_call(peer_id), self._loop ) try: return future.result(timeout=10.0) except Exception as e: logger.error(f"Accept voice call error: {e}") return False return False def reject_voice_call(self, peer_id: str): """拒绝语音通话(线程安全)""" if self._loop and self._running: self.client.reject_voice_call(peer_id) def refresh_users(self): """刷新用户列表(线程安全)""" if self._loop and self._running: asyncio.run_coroutine_threadsafe( self.client.refresh_online_users(), self._loop ) class P2PChatGUI(QObject): """P2P 聊天 GUI 应用程序""" # 信号定义 - 用于跨线程通信 file_received_signal = pyqtSignal(str, str, str) # sender_id, file_name, file_path def __init__(self, config: ClientConfig): super().__init__() self.config = config self.app: Optional[QApplication] = None self.main_window: Optional[MainWindow] = None self.client: Optional[P2PClientApp] = None self.worker: Optional[AsyncWorker] = None self._current_user: Optional[UserInfo] = None self._current_chat_peer: Optional[str] = None # 连接信号到槽 self.file_received_signal.connect(self._handle_file_received_ui) def run(self) -> int: """运行 GUI 应用程序""" # 创建 Qt 应用 self.app = QApplication(sys.argv) self.app.setApplicationName("P2P Chat") self.app.setApplicationVersion("0.1.0") # 设置默认字体 font = QFont("Microsoft YaHei", 10) self.app.setFont(font) # 设置样式 self.app.setStyle("Fusion") # 应用退出时清理资源 self.app.aboutToQuit.connect(self.cleanup) # 显示登录对话框 if not self._show_login(): return 0 # 创建主窗口 self._create_main_window() # 连接到服务器 self._connect_to_server() # 运行应用 return self.app.exec() def _show_login(self) -> bool: """显示登录对话框""" dialog = LoginDialog() # 设置服务器地址 dialog._login_server_input.setText( f"{self.config.server_host}:{self.config.server_port}" ) dialog._reg_server_input.setText( f"{self.config.server_host}:{self.config.server_port}" ) if dialog.exec(): self._current_user = dialog.get_user_info() host, port = dialog.get_server_address() self.config.server_host = host self.config.server_port = port return True return False def _create_main_window(self): """创建主窗口""" self.main_window = MainWindow(self.config) if self._current_user: self.main_window.set_current_user(self._current_user) # 创建并设置联系人列表组件 self._contact_list_widget = ContactListWidget() self.main_window.set_contact_list_widget(self._contact_list_widget) # 创建并设置聊天组件 self._chat_widget = ChatWidget() self.main_window.set_chat_widget(self._chat_widget) # 连接聊天组件信号 self._chat_widget.message_sent.connect(self._on_send_message) self._chat_widget.file_send_requested.connect(lambda peer_id: self._on_send_file()) self._chat_widget.image_send_requested.connect(lambda peer_id: self._on_send_image()) self._chat_widget.voice_call_requested.connect(lambda peer_id: self._on_voice_call()) self._chat_widget.voice_call_end_requested.connect(self._on_end_voice_call) # 连接联系人列表信号 self._contact_list_widget.contact_selected.connect(self._on_contact_selected) self._contact_list_widget.contact_double_clicked.connect(self._on_contact_double_clicked) self._contact_list_widget.refresh_requested.connect(self._refresh_users) self._contact_list_widget.lan_discovery_requested.connect(self._on_lan_discovery) # 设置系统托盘 try: tray = SystemTrayManager(self.main_window) self.main_window.set_system_tray(tray) except Exception as e: logger.warning(f"Failed to create system tray: {e}") # 连接菜单动作 self._connect_menu_actions() self.main_window.show() def _connect_menu_actions(self): """连接菜单动作""" # 发送文件 self.main_window._send_file_action.triggered.connect(self._on_send_file) # 发送图片 self.main_window._send_image_action.triggered.connect(self._on_send_image) # 语音通话 self.main_window._voice_call_action.triggered.connect(self._on_voice_call) def _connect_to_server(self): """连接到服务器""" if not self._current_user: return # 更新状态 self.main_window.connection_state_changed.emit("连接中...") # 创建客户端 self.client = P2PClientApp(self.config) # 添加文件接收回调 self.client.add_file_received_callback(self._on_file_received) # 创建工作线程 self.worker = AsyncWorker(self.client, self._current_user) self.worker.connected.connect(self._on_connected) self.worker.disconnected.connect(self._on_disconnected) self.worker.message_received.connect(self._on_message_received) self.worker.user_list_updated.connect(self._on_user_list_updated) self.worker.state_changed.connect(self._on_state_changed) # 启动工作线程 self.worker.start() def _on_connected(self, success: bool, message: str): """连接结果回调""" if success: self.main_window.connection_state_changed.emit("已连接") self.main_window._statusbar.showMessage(f"已连接到服务器") logger.info("Connected to server") # 刷新用户列表 QTimer.singleShot(500, self._refresh_users) else: self.main_window.connection_state_changed.emit("连接失败") QMessageBox.critical( self.main_window, "连接失败", f"无法连接到服务器: {message}" ) def _on_disconnected(self, reason: str): """断开连接回调""" self.main_window.connection_state_changed.emit("已断开") self.main_window._statusbar.showMessage(f"连接断开: {reason}") # 询问是否重连 reply = QMessageBox.question( self.main_window, "连接断开", f"与服务器的连接已断开: {reason}\n\n是否尝试重新连接?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply == QMessageBox.StandardButton.Yes: self._connect_to_server() def _on_message_received(self, message: Message): """消息接收回调""" if message.msg_type == MessageType.TEXT: content = message.payload.decode('utf-8') sender = message.sender_id # 显示通知 if self.main_window.isMinimized() or not self.main_window.isActiveWindow(): self.main_window.show_notification( f"来自 {sender} 的消息", content[:50] + ("..." if len(content) > 50 else "") ) # 更新状态栏 self.main_window._statusbar.showMessage( f"收到来自 {sender} 的消息", 3000 ) # 将消息添加到聊天窗口显示 from datetime import datetime from shared.models import ChatMessage msg = ChatMessage( message_id=message.message_id, sender_id=sender, receiver_id=message.receiver_id, content_type=MessageType.TEXT, content=content, timestamp=datetime.fromtimestamp(message.timestamp), is_sent=True, is_read=False ) # 如果当前正在和发送者聊天,直接显示消息 if hasattr(self, '_chat_widget') and self._chat_widget: if self._current_chat_peer == sender: self._chat_widget.add_message(msg, is_self=False) logger.info(f"Message from {sender}: {content[:50]}...") elif message.msg_type == MessageType.VOICE_CALL_REQUEST: sender = message.sender_id logger.info(f"Voice call request from {sender}") self.main_window.show_notification( "来电", f"{sender} 正在呼叫你" ) # 显示来电对话框 self._show_incoming_call_dialog(sender) elif message.msg_type == MessageType.VOICE_CALL_ACCEPT: sender = message.sender_id logger.info(f"Voice call accepted by {sender}") self.main_window._statusbar.showMessage(f"通话已接通 - {sender}", 5000) self.main_window.show_notification("通话已接通", f"与 {sender} 的通话已建立") # 更新UI状态为通话中 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(True, "通话中...") elif message.msg_type == MessageType.VOICE_CALL_REJECT: sender = message.sender_id reason = message.payload.decode('utf-8') if message.payload else "对方拒绝" logger.info(f"Voice call rejected by {sender}: {reason}") self.main_window._statusbar.showMessage(f"呼叫被拒绝: {reason}", 5000) self.main_window.show_notification("呼叫被拒绝", f"{sender} 拒绝了你的通话请求") # 恢复UI状态 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(False) elif message.msg_type == MessageType.VOICE_CALL_END: sender = message.sender_id logger.info(f"Voice call ended by {sender}") self.main_window._statusbar.showMessage("通话已结束", 3000) self.main_window.show_notification("通话结束", f"与 {sender} 的通话已结束") # 恢复UI状态 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(False) def _on_user_list_updated(self, users: List[UserInfo]): """用户列表更新回调""" logger.info(f"Online users: {len(users)}") self.main_window._statusbar.showMessage( f"在线用户: {len(users)}", 3000 ) # 更新联系人列表(排除自己) if hasattr(self, '_contact_list_widget') and self._contact_list_widget: # 过滤掉当前用户自己 other_users = [ user for user in users if self._current_user is None or user.user_id != self._current_user.user_id ] self._contact_list_widget.set_contacts(other_users) logger.info(f"Contact list updated: {len(other_users)} contacts") # 更新连接模式显示 self._update_connection_modes() def _on_state_changed(self, state: str, reason: str): """状态变化回调""" state_text = { "disconnected": "已断开", "connecting": "连接中...", "connected": "已连接", "reconnecting": "重连中..." }.get(state, state) self.main_window.connection_state_changed.emit(state_text) def _refresh_users(self): """刷新用户列表""" if self.worker: self.worker.refresh_users() def _on_lan_discovery(self): """发现局域网用户""" self.main_window._statusbar.showMessage("正在发现局域网用户...", 5000) if self.worker and self.worker._loop and self.worker._running: future = asyncio.run_coroutine_threadsafe( self._discover_lan_peers_async(), self.worker._loop ) try: peers = future.result(timeout=5.0) if peers: self.main_window._statusbar.showMessage(f"发现 {len(peers)} 个局域网用户", 3000) self.main_window.show_notification("局域网发现", f"发现 {len(peers)} 个局域网用户") # 更新联系人列表中的连接模式 self._update_connection_modes() else: self.main_window._statusbar.showMessage("未发现局域网用户", 3000) except Exception as e: logger.error(f"LAN discovery error: {e}") self.main_window._statusbar.showMessage("局域网发现失败", 3000) async def _discover_lan_peers_async(self): """异步发现局域网用户""" if self.client: return await self.client.discover_lan_peers() return [] def _update_connection_modes(self): """更新联系人列表中的连接模式显示""" if not self.client or not hasattr(self, '_contact_list_widget'): return # 获取所有连接模式 connection_manager = self.client.connection_manager # 更新每个联系人的连接模式 for user_id in list(self._contact_list_widget._contacts.keys()): mode = connection_manager.get_connection_mode(user_id) mode_str = "p2p" if mode.value == "p2p" else "relay" self._contact_list_widget.update_connection_mode(user_id, mode_str) def _on_send_file(self): """发送文件""" from PyQt6.QtWidgets import QFileDialog, QMessageBox import os if not self._current_chat_peer: QMessageBox.warning(self.main_window, "提示", "请先选择一个联系人") return file_path, _ = QFileDialog.getOpenFileName( self.main_window, "选择文件", "", "所有文件 (*.*)" ) if file_path and self._current_chat_peer: self.main_window._statusbar.showMessage(f"正在发送文件...") if self.worker: success = self.worker.send_file(self._current_chat_peer, file_path) if success: self.main_window._statusbar.showMessage("文件发送成功", 3000) # 在聊天窗口显示发送的文件 self._add_sent_file_message(file_path, is_image=False) else: self.main_window._statusbar.showMessage("文件发送失败", 3000) def _on_send_image(self): """发送图片""" from PyQt6.QtWidgets import QFileDialog, QMessageBox import os if not self._current_chat_peer: QMessageBox.warning(self.main_window, "提示", "请先选择一个联系人") return file_path, _ = QFileDialog.getOpenFileName( self.main_window, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.gif *.bmp)" ) if file_path and self._current_chat_peer: self.main_window._statusbar.showMessage(f"正在发送图片...") if self.worker: success = self.worker.send_image(self._current_chat_peer, file_path) if success: self.main_window._statusbar.showMessage("图片发送成功", 3000) # 在聊天窗口显示发送的图片 self._add_sent_file_message(file_path, is_image=True) else: self.main_window._statusbar.showMessage("图片发送失败", 3000) def _add_sent_file_message(self, file_path: str, is_image: bool): """在聊天窗口添加发送的文件/图片消息""" from datetime import datetime from shared.models import ChatMessage content_type = MessageType.IMAGE if is_image else MessageType.FILE_REQUEST msg = ChatMessage( message_id=f"sent_file_{datetime.now().timestamp()}", sender_id=self._current_user.user_id if self._current_user else "", receiver_id=self._current_chat_peer, content_type=content_type, content=file_path, timestamp=datetime.now(), is_sent=True, is_read=False ) if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.add_message(msg, is_self=True) def _on_voice_call(self): """发起语音通话""" from PyQt6.QtWidgets import QMessageBox if not self._current_chat_peer: QMessageBox.warning(self.main_window, "提示", "请先选择一个联系人") return self.main_window._statusbar.showMessage(f"正在呼叫 {self._current_chat_peer}...") # 更新UI状态为呼叫中 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(True, "正在呼叫...") if self.worker: success = self.worker.start_voice_call(self._current_chat_peer) if success: self.main_window._statusbar.showMessage("正在等待对方接听...", 30000) else: self.main_window._statusbar.showMessage("呼叫失败", 3000) QMessageBox.warning(self.main_window, "呼叫失败", "无法发起语音通话,请检查网络连接") # 恢复UI状态 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(False) def _on_end_voice_call(self): """结束语音通话""" logger.info("Ending voice call from GUI") if self.worker: self.worker.end_voice_call() self.main_window._statusbar.showMessage("通话已结束", 3000) # 更新UI状态 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(False) def _show_incoming_call_dialog(self, caller_id: str): """显示来电对话框""" from PyQt6.QtWidgets import QMessageBox logger.info(f"Showing incoming call dialog for {caller_id}") reply = QMessageBox.question( self.main_window, "来电", f"{caller_id} 正在呼叫你,是否接听?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.Yes # 默认选择接听 ) if reply == QMessageBox.StandardButton.Yes: # 接听 logger.info(f"Accepting call from {caller_id}") if self.worker and self.worker._loop and self.worker._running: future = asyncio.run_coroutine_threadsafe( self.client.accept_voice_call(caller_id), self.worker._loop ) try: success = future.result(timeout=10.0) if success: self.main_window._statusbar.showMessage("通话已接通", 3000) logger.info(f"Call accepted successfully with {caller_id}") # 更新UI状态为通话中 if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_call_state(True, "通话中...") else: self.main_window._statusbar.showMessage("接听失败", 3000) logger.error(f"Failed to accept call from {caller_id}") except Exception as e: logger.error(f"Accept call error: {e}") self.main_window._statusbar.showMessage(f"接听失败: {e}", 3000) else: # 拒绝 logger.info(f"Rejecting call from {caller_id}") if self.client: self.client.reject_voice_call(caller_id) self.main_window._statusbar.showMessage("已拒绝来电", 3000) def _on_file_received(self, sender_id: str, file_name: str, file_path: str): """文件接收完成回调 - 使用信号在主线程中更新GUI""" logger.info(f"GUI: File received callback triggered - {sender_id}, {file_name}, {file_path}") # 使用信号在主线程中执行 GUI 更新 self.file_received_signal.emit(sender_id, file_name, file_path) def _handle_file_received_ui(self, sender_id: str, file_name: str, file_path: str): """在主线程中处理文件接收的UI更新""" import os from datetime import datetime from shared.models import ChatMessage logger.info(f"GUI: _handle_file_received_ui called - sender: {sender_id}, current_peer: {self._current_chat_peer}") # 判断是否是图片 image_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'} ext = os.path.splitext(file_name)[1].lower() is_image = ext in image_extensions # 显示通知 if is_image: self.main_window.show_notification("收到图片", f"来自 {sender_id}: {file_name}") else: self.main_window.show_notification("收到文件", f"来自 {sender_id}: {file_name}") self.main_window._statusbar.showMessage(f"收到文件: {file_name},已保存到 {file_path}", 5000) # 创建消息对象 content_type = MessageType.IMAGE if is_image else MessageType.FILE_REQUEST msg = ChatMessage( message_id=f"file_{datetime.now().timestamp()}", sender_id=sender_id, receiver_id=self._current_user.user_id if self._current_user else "", content_type=content_type, content=file_path, timestamp=datetime.now(), is_sent=True, is_read=False ) # 如果当前没有打开聊天窗口或不是和发送者聊天,自动切换到发送者 if self._current_chat_peer != sender_id: logger.info(f"GUI: Switching to chat with {sender_id}") self._current_chat_peer = sender_id if hasattr(self, '_chat_widget') and self._chat_widget: # 获取发送者信息 peer_info = None if hasattr(self, '_contact_list_widget') and self._contact_list_widget: peer_info = self._contact_list_widget.get_contact(sender_id) self._chat_widget.set_peer(sender_id, peer_info) self.main_window.set_current_chat_peer(sender_id) # 在聊天窗口显示 if hasattr(self, '_chat_widget') and self._chat_widget: logger.info(f"GUI: Adding file message to chat widget") self._chat_widget.add_message(msg, is_self=False) def _on_contact_selected(self, user_id: str): """联系人选中回调 - 打开聊天窗口""" self._current_chat_peer = user_id logger.debug(f"Contact selected: {user_id}") # 获取联系人信息并打开聊天窗口 if hasattr(self, '_contact_list_widget') and self._contact_list_widget: peer_info = self._contact_list_widget.get_contact(user_id) if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_peer(user_id, peer_info) self.main_window.set_current_chat_peer(user_id) def _on_contact_double_clicked(self, user_id: str): """联系人双击回调 - 打开聊天""" self._current_chat_peer = user_id logger.info(f"Opening chat with: {user_id}") # 获取联系人信息并打开聊天窗口 if hasattr(self, '_contact_list_widget') and self._contact_list_widget: peer_info = self._contact_list_widget.get_contact(user_id) if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.set_peer(user_id, peer_info) self.main_window.set_current_chat_peer(user_id) self.main_window._statusbar.showMessage(f"与 {user_id} 聊天", 3000) def _on_send_message(self, peer_id: str, content: str): """发送消息回调""" if self.worker: success = self.worker.send_message(peer_id, content) if success: # 添加消息到聊天窗口显示 from datetime import datetime from shared.models import ChatMessage msg = ChatMessage( message_id=f"msg_{datetime.now().timestamp()}", sender_id=self._current_user.user_id if self._current_user else "", receiver_id=peer_id, content_type=MessageType.TEXT, content=content, timestamp=datetime.now(), is_sent=True ) if hasattr(self, '_chat_widget') and self._chat_widget: self._chat_widget.add_message(msg, is_self=True) logger.info(f"Message sent to {peer_id}") else: self.main_window._statusbar.showMessage("消息发送失败", 3000) def cleanup(self): """清理资源""" logger.info("Cleaning up resources...") if self.worker: self.worker.stop() self.worker.wait(3000) self.worker = None if self.client: # 在新的事件循环中停止客户端 loop = asyncio.new_event_loop() try: loop.run_until_complete(self.client.stop()) except Exception as e: logger.error(f"Error stopping client: {e}") finally: loop.close() self.client = None logger.info("Cleanup completed") def parse_args() -> argparse.Namespace: """解析命令行参数""" parser = argparse.ArgumentParser( description='P2P Chat Client (GUI)', formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument( '--server', '-s', type=str, default=None, help='服务器地址 (默认: 127.0.0.1)' ) parser.add_argument( '--port', '-p', type=int, default=None, help='服务器端口 (默认: 8888)' ) parser.add_argument( '--debug', action='store_true', help='启用调试模式' ) return parser.parse_args() def main() -> int: """主函数""" args = parse_args() # 设置日志级别 if args.debug: logging.getLogger().setLevel(logging.DEBUG) # 加载配置 config = load_client_config() # 命令行参数覆盖配置 if args.server: config.server_host = args.server if args.port: config.server_port = args.port # 创建并运行 GUI gui = P2PChatGUI(config) try: exit_code = gui.run() return exit_code except KeyboardInterrupt: logger.info("Application interrupted") gui.cleanup() return 0 except Exception as e: logger.error(f"Application error: {e}") gui.cleanup() return 1 if __name__ == "__main__": sys.exit(main())