import sys
import psutil
import pyshark
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QWidget,
    QTableWidget, QTableWidgetItem, QHeaderView, QComboBox,
    QHBoxLayout, QLabel, QLineEdit
)
from PySide6.QtCore import Qt, QThread, Signal, QTimer
from PySide6.QtGui import QIcon, QPixmap
from collections import defaultdict
import win32gui
import win32ui
from PIL import Image
import threading

def make_transparent(image):
    image = image.convert("RGBA")
    datas = image.getdata()
    new_data = []
    for item in datas:
        if item[0] == 0 and item[1] == 0 and item[2] == 0:
            new_data.append((255, 255, 255, 0))
        else:
            new_data.append(item)
    image.putdata(new_data)
    return image

class NetworkMonitorThread(QThread):
    update_signal = Signal(list)

    def __init__(self, interface, parent=None):
        super().__init__(parent)
        self.running = True
        self.process_net_io = defaultdict(lambda: {'bytes_recv': 0, 'bytes_sent': 0, 'name': ''})
        self.lock = threading.Lock()
        self.capture = None
        self.interface = interface

    def run(self):
        if not self.interface:
            print("未选择网络接口，无法进行数据包捕获。")
            return

        try:
            self.capture = pyshark.LiveCapture(interface=self.interface, bpf_filter='tcp or udp')
            print(f"开始在接口 {self.interface} 上捕获数据包...")

            for packet in self.capture.sniff_continuously():
                if not self.running:
                    self.capture.close()
                    break
                try:
                    if 'IP' not in packet:
                        continue
                    src_ip = packet.ip.src
                    dst_ip = packet.ip.dst
                    protocol = packet.transport_layer
                    src_port = packet[packet.transport_layer].srcport
                    dst_port = packet[packet.transport_layer].dstport
                    packet_length = int(packet.length)

                    # 查找与源 IP:port 相关的进程
                    src_pid = self.get_pid(src_ip, src_port, protocol)
                    if src_pid:
                        with self.lock:
                            self.process_net_io[src_pid]['bytes_sent'] += packet_length

                    # 查找与目标 IP:port 相关的进程
                    dst_pid = self.get_pid(dst_ip, dst_port, protocol)
                    if dst_pid:
                        with self.lock:
                            self.process_net_io[dst_pid]['bytes_recv'] += packet_length

                except Exception as e:
                    print(f"处理数据包时出错: {e}")

        except Exception as e:
            print(f"初始化数据包捕获时出错: {e}")
            return

        # 捕获结束后更新 UI
        self.update_ui()

    def get_pid(self, ip, port, protocol):
        try:
            for proc in psutil.process_iter(['pid', 'name', 'connections']):
                for conn in proc.info['connections']:
                    if conn.laddr and conn.laddr.ip == ip and conn.laddr.port == int(port):
                        return proc.info['pid']
            return None
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            return None

    def update_ui(self):
        with self.lock:
            data = []
            for pid, io in self.process_net_io.items():
                if io['bytes_recv'] > 0 or io['bytes_sent'] > 0:
                    try:
                        proc = psutil.Process(pid)
                        name = proc.name()
                        data.append((
                            name,
                            io['bytes_recv'] / 1024,  # KB
                            io['bytes_sent'] / 1024,  # KB
                            io['bytes_recv'] / (1024 * 1024),  # MB
                            io['bytes_sent'] / (1024 * 1024)   # MB
                        ))
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        continue
            # 重置统计
            self.process_net_io = defaultdict(lambda: {'bytes_recv': 0, 'bytes_sent': 0, 'name': ''})
            self.update_signal.emit(data)

    def stop(self):
        self.running = False
        if self.capture:
            self.capture.close()

class NetworkMonitor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("网络流量监控")
        self.setGeometry(100, 100, 800, 600)

        self.table = QTableWidget(self)
        self.table.setColumnCount(6)
        self.table.setHorizontalHeaderLabels(["图标", "进程", "下载速度 (KB/s)", "上传速度 (KB/s)", "下载量 (MB)", "上传量 (MB)"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        layout = QVBoxLayout()
        layout.addWidget(self.table)

        self.sort_combo = QComboBox()
        self.sort_combo.addItem("按下载速度排序")
        self.sort_combo.addItem("按进程名称排序")
        self.sort_combo.currentIndexChanged.connect(self.update_network_info)

        self.filter_input = QLineEdit(self)
        self.filter_input.setPlaceholderText("按进程名称过滤")
        self.filter_input.textChanged.connect(self.update_network_info)

        sort_layout = QHBoxLayout()
        sort_layout.addWidget(QLabel("排序方式:"))
        sort_layout.addWidget(self.sort_combo)
        sort_layout.addWidget(QLabel("过滤:"))
        sort_layout.addWidget(self.filter_input)
        layout.addLayout(sort_layout)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # 在主线程中枚举网络接口
        try:
            interfaces = pyshark.LiveCapture.interfaces
            print("可用的网络接口:")
            for idx, iface in enumerate(interfaces):
                print(f"{idx}: {iface}")
        except Exception as e:
            print(f"获取接口时出错: {e}")
            interfaces = []

        # 选择第一个非回环且非etwdump的接口
        selected_interface = None
        for iface in interfaces:
            if "Loopback" not in iface and "etwdump" not in iface:
                selected_interface = iface
                break

        if not selected_interface:
            print("未找到合适的网络接口进行捕获。")
        else:
            print(f"选择的接口: {selected_interface}")
            self.network_thread = NetworkMonitorThread(selected_interface, self)
            self.network_thread.update_signal.connect(self.update_table)
            self.network_thread.start()

        self.icon_cache = {}
        self.default_icon = QIcon("default_icon.png")  # 确保此路径有默认图标

        # 定时器，用于定期刷新表格
        self.ui_timer = QTimer()
        self.ui_timer.timeout.connect(self.refresh_table)
        self.ui_timer.start(2000)  # 每2秒刷新一次

        # 存储最新的数据
        self.latest_data = []

    def update_table(self, sorted_data):
        self.latest_data = sorted_data

    def refresh_table(self):
        if not self.latest_data:
            return

        filter_text = self.filter_input.text().lower()
        filtered_data = [data for data in self.latest_data if filter_text in data[0].lower() or not filter_text]

        sort_by = self.sort_combo.currentIndex()
        if sort_by == 0:
            # 按下载速度排序
            filtered_data.sort(key=lambda x: x[1], reverse=True)
        else:
            # 按进程名称排序
            filtered_data.sort(key=lambda x: x[0])

        self.table.setRowCount(len(filtered_data))
        for row, data in enumerate(filtered_data):
            process_name = data[0]
            icon_item = QTableWidgetItem()
            icon = self.get_exe_icon(process_name)
            if icon:
                icon_item.setIcon(icon)
            else:
                icon_item.setIcon(self.default_icon)  # 使用默认图标
            self.table.setItem(row, 0, icon_item)

            for col, value in enumerate(data):
                self.table.setItem(row, col + 1, QTableWidgetItem(f"{value:.2f}" if isinstance(value, float) else str(value)))

    def get_exe_icon(self, process_name):
        if process_name in self.icon_cache:
            return self.icon_cache[process_name]

        try:
            exe_path = None
            for proc in psutil.process_iter(['pid', 'name', 'exe']):
                if proc.info['name'] == process_name:
                    exe_path = proc.info['exe']
                    break

            if not exe_path:
                return None

            large, small = win32gui.ExtractIconEx(exe_path, 0)
            if not large:
                return None

            hdc = win32gui.GetDC(0)
            hdc = win32ui.CreateDCFromHandle(hdc)
            hbmp = win32ui.CreateBitmap()
            hbmp.CreateCompatibleBitmap(hdc, 32, 32)

            hdc = hdc.CreateCompatibleDC()
            hdc.SelectObject(hbmp)
            hdc.DrawIcon((0, 0), large[0])

            bmpinfo = hbmp.GetInfo()
            bmpstr = hbmp.GetBitmapBits(True)
            img = Image.frombuffer(
                'RGB',
                (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
                bmpstr, 'raw', 'BGRX', 0, 1
            )

            img = make_transparent(img)
            img_path = f'temp_icon_{process_name}.png'
            img.save(img_path)
            pixmap = QPixmap(img_path)

            icon = QIcon(pixmap)
            self.icon_cache[process_name] = icon
            return icon
        except Exception as e:
            print(f"提取图标时出错: {e}")
            return None

    def update_network_info(self):
        pass

    def closeEvent(self, event):
        if hasattr(self, 'network_thread') and self.network_thread.isRunning():
            self.network_thread.stop()
            self.network_thread.wait()PS C:\Users\duckb> ipconfig /all

Windows IP 配置

   主机名  . . . . . . . . . . . . . : dd
   主 DNS 后缀 . . . . . . . . . . . :
   节点类型  . . . . . . . . . . . . : 混合
   IP 路由已启用 . . . . . . . . . . : 否
   WINS 代理已启用 . . . . . . . . . : 否
   DNS 后缀搜索列表  . . . . . . . . : lan

以太网适配器 以太网 2:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : TAP-Windows Adapter V9
   物理地址. . . . . . . . . . . . . : 00-FF-22-D6-81-02
   DHCP 已启用 . . . . . . . . . . . : 是
   自动配置已启用. . . . . . . . . . : 是

以太网适配器 以太网:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : Realtek PCIe GbE Family Controller
   物理地址. . . . . . . . . . . . . : 8C-16-45-5C-16-BE
   DHCP 已启用 . . . . . . . . . . . : 是
   自动配置已启用. . . . . . . . . . : 是

无线局域网适配器 本地连接* 1:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter
   物理地址. . . . . . . . . . . . . : 72-C9-4E-6C-36-1B
   DHCP 已启用 . . . . . . . . . . . : 是
   自动配置已启用. . . . . . . . . . : 是

无线局域网适配器 本地连接* 12:

   媒体状态  . . . . . . . . . . . . : 媒体已断开连接
   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : Microsoft Wi-Fi Direct Virtual Adapter #4
   物理地址. . . . . . . . . . . . . : F2-C9-4E-40-20-10
   DHCP 已启用 . . . . . . . . . . . : 是
   自动配置已启用. . . . . . . . . . : 是

无线局域网适配器 WLAN:

   连接特定的 DNS 后缀 . . . . . . . : lan
   描述. . . . . . . . . . . . . . . : Realtek 8821CE Wireless LAN 802.11ac PCI-E NIC
   物理地址. . . . . . . . . . . . . : 70-C9-4E-EA-E3-73
   DHCP 已启用 . . . . . . . . . . . : 是
   自动配置已启用. . . . . . . . . . : 是
   IPv4 地址 . . . . . . . . . . . . : 192.168.2.145(首选)
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   获得租约的时间  . . . . . . . . . : 2024年10月3日 7:17:37
   租约过期的时间  . . . . . . . . . : 2024年10月4日 7:17:38
   默认网关. . . . . . . . . . . . . : 192.168.2.1
   DHCP 服务器 . . . . . . . . . . . : 192.168.2.1
   DNS 服务器  . . . . . . . . . . . : 192.168.2.1
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

以太网适配器 vEthernet (Default Switch):

   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : Hyper-V Virtual Ethernet Adapter
   物理地址. . . . . . . . . . . . . : 00-15-5D-C4-A9-88
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::9cce:37af:fa24:8c52%41(首选)
   IPv4 地址 . . . . . . . . . . . . : 172.19.176.1(首选)
   子网掩码  . . . . . . . . . . . . : 255.255.240.0
   默认网关. . . . . . . . . . . . . :
   DHCPv6 IAID . . . . . . . . . . . : 687871325
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-2D-6C-7A-5B-8C-16-45-5C-16-BE
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用

以太网适配器 vEthernet (WSL):

   连接特定的 DNS 后缀 . . . . . . . :
   描述. . . . . . . . . . . . . . . : Hyper-V Virtual Ethernet Adapter #2
   物理地址. . . . . . . . . . . . . : 00-15-5D-14-F9-FA
   DHCP 已启用 . . . . . . . . . . . : 否
   自动配置已启用. . . . . . . . . . : 是
   本地链接 IPv6 地址. . . . . . . . : fe80::dd67:778f:b27a:40ac%62(首选)
   IPv4 地址 . . . . . . . . . . . . : 172.27.96.1(首选)
   子网掩码  . . . . . . . . . . . . : 255.255.240.0
   默认网关. . . . . . . . . . . . . :
   DHCPv6 IAID . . . . . . . . . . . : 1040192861
   DHCPv6 客户端 DUID  . . . . . . . : 00-01-00-01-2D-6C-7A-5B-8C-16-45-5C-16-BE
   TCPIP 上的 NetBIOS  . . . . . . . : 已启用
PS C:\Users\duckb>
        super().closeEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = NetworkMonitor()
    window.show()
    sys.exit(app.exec())
代码运行后提示获取接口时出错: type object 'LiveCapture' has no attribute 'interfaces'
未找到合适的网络接口进行捕获。。。。。。。另外，我提供一些额外信息，不知道有用没用。中文回复我。