利用Qt及cef实现流量监控控件

501 阅读5分钟

前言

最近学习了开源网速监控悬浮窗软件(github.com/zhongyang21… ),觉得功能很强大,但由于其是基于Windows平台开发的,因此决定采用Qt重新实现该软件的部分功能,并使用CEF与QCefView实现Qt与前端网页的简单交互。文章内容主要包括:1、流量、cpu及内存检测功能及主界面实现;2、Qt与前端交互及cef窗口实现。

一、流量、cpu及内存检测功能及主界面实现

Windows平台下获取网络流量可以参考www.cnblogs.com/chaikefusib… ,主要函数如下,为实现获取每秒的上传网络与下载网络,可通过定时器m_timer.setInterval(1000);connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));实现。

//网络流量获取
PMIB_IFTABLE m_pTable = nullptr;
DWORD m_dwAdapters = 0;
GetIfTable(m_pTable, &m_dwAdapters, TRUE);
DwOctets<DWORD> dwOctets;
for (UINT i = 0; i < m_pTable->dwNumEntries; i++)
{
    MIB_IFROW Row = m_pTable->table[i];
    dwOctets.m_dwInOctets += Row.dwInOctets;
    dwOctets.m_dwOutOctets += Row.dwOutOctets;
}
DwOctets<double> dwbands;
dwbands.m_dwInOctets = (dwOctets.m_dwInOctets - m_lastDwOctets.m_dwInOctets) / 1024;
dwbands.m_dwOutOctets = (dwOctets.m_dwOutOctets - m_lastDwOctets.m_dwOutOctets) / 1024;

获取cpu、内存占有率函数分别如下:

inline __int64 NetCPUMemInfo::Filetime2Int64(const FILETIME &ftime)
{
    LARGE_INTEGER li;
    li.LowPart = ftime.dwLowDateTime;
    li.HighPart = ftime.dwHighDateTime;
    return li.QuadPart;
}
double NetCPUMemInfo::getCpuUsage()
{
    SystemTime systemTime;
    GetSystemTimes(&systemTime.m_idleTime, &systemTime.m_kernelTime, &systemTime.m_userTime);
    __int64 idle = Filetime2Int64(systemTime.m_idleTime) - Filetime2Int64(m_lastSystemTime.m_idleTime);
    __int64 kernel = Filetime2Int64(systemTime.m_kernelTime) - Filetime2Int64(m_lastSystemTime.m_kernelTime);
    __int64 user = Filetime2Int64(systemTime.m_userTime) - Filetime2Int64(m_lastSystemTime.m_userTime);
    m_lastSystemTime = systemTime;
    return 100.0 * (kernel + user - idle) / (kernel + user);
}
double NetCPUMemInfo::getMemUsage()
{
    MEMORYSTATUSEX memStatus;
    memStatus.dwLength = sizeof(memStatus);
    GlobalMemoryStatusEx(&memStatus);
    return 100.0 * (memStatus.ullTotalPhys - memStatus.ullAvailPhys) / memStatus.ullTotalPhys;
}

主界面逻辑及各函数功能说明如下:

//monitor_widget.h
#ifndef __MONITOR_WIDGET_H__
#define __MONITOR_WIDGET_H__

#include "traffic_monitor.h"
#include "http_client/http_client.h"
#include "cef_widget.h"
#include <QWidget>
#include <QLabel>
#include <QTimer>

using namespace base_common;

class MonitorWidget : public QWidget
{
    Q_OBJECT
public:
    MonitorWidget(QWidget *parent = nullptr);
    ~MonitorWidget();
    bool getFixedValue();
    double getTransparency();
protected:
    bool event(QEvent* event) override;
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
private slots:
    void onTimeout();
private:
    void initUI();
    void loadBgImage();
    QString getFormatDwOctets(double dwOctets);
private:
    QPixmap m_bgImage;
    qint8 m_currentIndex = 0;
    QTimer m_timer;
    std::unique_ptr<NetCPUMemInfo> m_netCPUMemInfoPtr;
    bool m_bLeftButton = false;
    QPoint m_point;
    CefWidget* m_cefWidget = nullptr;
    double m_transparency = 1.0;
    bool m_fixedToScreen = false;
private:
    NetCPUMemInfo::DwOctets<double> m_dwOctets;
    int m_cpuUsage = 0;
    int m_memUsage = 0;
};
#endif
//monitor_widget.cpp
#include "monitor_widget.h"
#include "config/config.h"
#include "thread_pool/thread_pool.h"
#include <QPainter>
#include <QPainterPath>
#include <QGuiApplication>
#include <QScreen>
#include <QMouseEvent>
#include <QGridLayout>
#include <QFileInfo>

static const QString BgImageSuffix = "/project_resource/traffic_monitor/bgImage.jpg";
QEvent::Type TaskFinishEvent = (QEvent::Type)QEvent::registerEventType();

class downloadImageTask : public IThreadHandle {
public:
    downloadImageTask(const QString& url, QWidget* parent = nullptr) 
        : m_url(url), m_parentWidgetPtr(parent) {}

    virtual void threadhandle() { // 异步下载背景图片,完成后通过postEvent通知主界面
        std::unique_ptr<HttpClient> httpClientPtr(new HttpClient(m_url.toStdString()));
        QString bgImagePath = QCoreApplication::applicationDirPath() + BgImageSuffix;
        if (httpClientPtr->getResquestToFile(bgImagePath.toStdString(), "", false)) {
            if (m_parentWidgetPtr)
                QCoreApplication::postEvent(m_parentWidgetPtr, new QEvent(TaskFinishEvent));
        }
    }
private:
    QString m_url;
    QPointer<QWidget> m_parentWidgetPtr;
};

MonitorWidget::MonitorWidget(QWidget *parent) : QWidget(parent) {
    initUI();
    m_netCPUMemInfoPtr.reset(new NetCPUMemInfo);
    m_timer.setInterval(1000);
    m_timer.start();
    connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
}

MonitorWidget::~MonitorWidget(){}

bool MonitorWidget::getFixedValue() {
    return m_fixedToScreen;
}

double MonitorWidget::getTransparency() {
    return m_transparency;
}

void MonitorWidget::initUI() {
    setAttribute(Qt::WA_TranslucentBackground);
    setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
    loadBgImage();
    setFixedSize(QSize(220, 43));
    QRect screenRect = QGuiApplication::primaryScreen()->geometry();
    move(screenRect.width() * 0.75, screenRect.height() * 0.75);
}

void MonitorWidget::loadBgImage() {
    QString bgImagePath = QCoreApplication::applicationDirPath() + BgImageSuffix;
    QFileInfo fileInfo(bgImagePath);
    if (fileInfo.exists())
        m_bgImage = QPixmap(bgImagePath).scaled(220, 43, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}

bool MonitorWidget::event(QEvent* event) {
    if (event->type() == TaskFinishEvent) {
        loadBgImage();
        repaint();
    }
    return QWidget::event(event);
}

void MonitorWidget::paintEvent(QPaintEvent *event) {
    Q_UNUSED(event); // 重写paintEvent函数,绘制不规则窗口
    setWindowOpacity(m_transparency);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setBrush(m_bgImage);
    painter.setPen(Qt::transparent);
    painter.drawRoundedRect(rect(), 5, 5);
    painter.setPen(Qt::black);

    QFont font;
    font.setFamily("Microsoft YaHei");
    font.setPointSize(10);
    painter.setFont(font);

    QString dwOutOctets = QString(tr("upload") + ": %1").arg(getFormatDwOctets(m_dwOctets.m_dwOutOctets));
    QString dwInOctets = QString(tr("download") + ": %1").arg(getFormatDwOctets(m_dwOctets.m_dwInOctets));
    QString cpuUsage = QString("CPU: %1\%").arg(m_cpuUsage);
    QString memoryUsage = QString(tr("memory") + ": %1\%").arg(m_memUsage);

    painter.drawText(QRect(10, 3, 120, 18), Qt::AlignLeft, dwOutOctets);
    painter.drawText(QRect(120, 3, 120, 18), Qt::AlignLeft, dwInOctets);
    painter.drawText(QRect(10, 23, 120, 18), Qt::AlignLeft, cpuUsage);
    painter.drawText(QRect(120, 23, 120, 18), Qt::AlignLeft, memoryUsage);
}

void MonitorWidget::mousePressEvent(QMouseEvent *event) {
    if (!m_fixedToScreen && event->button() == Qt::LeftButton) {
        m_bLeftButton = true;
        m_point = event->globalPos() - pos();
    }
    return QWidget::mousePressEvent(event);
}

void MonitorWidget::mouseReleaseEvent(QMouseEvent *event) {
    if (event->button() == Qt::LeftButton)
        m_bLeftButton = false;
    else if (event->button() == Qt::RightButton) {
        m_cefWidget = new CefWidget(this);
        m_cefWidget->show();
        m_cefWidget->move(pos() + QPoint(size().width() / 2, size().height()));
        connect(m_cefWidget, &CefWidget::sigSkinChange, this, [this](const QString& imageUrl) {
            CThreadPool::getInstance()->pushTask(std::shared_ptr<IThreadHandle>(new downloadImageTask(imageUrl, this)), TASK_LEVEL_MIDUM);
        });
        connect(m_cefWidget, &CefWidget::sigFixedToScreen, this, [this]() {
            m_fixedToScreen = !m_fixedToScreen;
        });
        connect(m_cefWidget, &CefWidget::sigTransparencyChange, this, [this](int value) {
            m_transparency = qMax(0.1, (double)(99 - value) / 99);
            repaint();
        });
    }
    return QWidget::mouseReleaseEvent(event);
}

void MonitorWidget::mouseMoveEvent(QMouseEvent *event) {
    if (m_bLeftButton) //无边框条件下拖到窗口
        move(event->globalPos() - m_point);
    return QWidget::mouseMoveEvent(event);
}

void MonitorWidget::onTimeout() {
    m_dwOctets = m_netCPUMemInfoPtr->getNetOctets();//每秒查询上传流量与下载流量
    m_cpuUsage = m_netCPUMemInfoPtr->getCpuUsage();
    m_memUsage = m_netCPUMemInfoPtr->getMemUsage();
    repaint();
}

QString MonitorWidget::getFormatDwOctets(double dwOctets) {
    QString dwOctetsStr;
    if (dwOctets < 10)
        dwOctetsStr = QString::number(dwOctets, 'f', 2) + " KB/s";
    else if (dwOctets < 100)
        dwOctetsStr = QString::number(dwOctets, 'f', 1) + " KB/s";
    else if (dwOctets < 1024)
        dwOctetsStr = QString::number(dwOctets, 'f', 0) + " KB/s";
    else
        dwOctetsStr = QString::number(dwOctets / 1024, 'f', 2) + " MB/s";
    return dwOctetsStr;
}

最终得到主界面如下:

image.png

二、Qt与前端交互及cef窗口实现

Qt与前端的交互可以参考Qt + CEF + Node.js 桌面开发实战 - 杨科山 - 掘金小册 (juejin.cn),对于简单应用,更推荐使用Qt自带的QWebEngine,对于复杂应用,更推荐CEF(github.com/chromiumemb… )。但是第二种方式,Qt调用cef接口与前端交互,加载一个简单的网页也涉及到很多逻辑,个人有些没看懂,于是采用QCefView(cefview.github.io/QCefView/zh… )来实现Qt与前端的交互:

//cef_widget.h
#ifndef __CEF_WIDGET_H__
#define __CEF_WIDGET_H__

#include <QWidget>
#include <QPointer>
#include "QCefView.h"
#include "QCefContext.h"

class MonitorWidget;
class CefWidget : public QWidget {
    Q_OBJECT
public:
    CefWidget(QWidget *parent = Q_NULLPTR);
    ~CefWidget();
    void initCef();
protected:
    void paintEvent(QPaintEvent* event) override;
signals:
    void sigSkinChange(const QString& imageUrl);
    void sigFixedToScreen();
    void sigTransparencyChange(int value);
private slots:
    void onInvokeMethod(int browserId, int frameId, const QString& method, const QVariantList& arguments);
    void onQCefQueryRequest(int browserId, int frameId, const QCefQuery& query);
private:
    void sendCefEvent(const QString& cefEvent, const QVariant& value);
private:
    QCefView* m_cefViewWidget = nullptr;
    QPointer<MonitorWidget> m_monitorPtr;
};
#endif
//cef_widget.cpp
#include "cef_widget.h"
#include "monitor_widget.h"
#include "config/config.h"
#include <QGuiApplication>
#include <QScreen>
#include <QVBoxLayout>
#include <QLabel>
#include <QDir>
#include <QPainter>
#include <QPainterPath>

CefWidget::CefWidget(QWidget* parent)
    : QWidget(parent)
    , m_monitorPtr(qobject_cast<MonitorWidget*>(parent)) {
    setAttribute(Qt::WA_TranslucentBackground);
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowFlags(windowFlags() | Qt::Popup | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
    resize(135, 140);
    initCef();
}

CefWidget::~CefWidget() {
    if (m_cefViewWidget)
        delete m_cefViewWidget;
}

void CefWidget::initCef() {
    QCefSetting setting;
    std::string cefWidgetUrl = Config::getInstance()->getTrafficMonitorConfig()->m_cefWidgetUrl;
    m_cefViewWidget = new QCefView(cefWidgetUrl.c_str(), &setting, this);
    QGridLayout* layout = new QGridLayout(this);
    layout->addWidget(m_cefViewWidget);
    connect(m_cefViewWidget, &QCefView::invokeMethod, this, &CefWidget::onInvokeMethod);
    connect(m_cefViewWidget, &QCefView::cefQueryRequest, this, &CefWidget::onQCefQueryRequest);
}

void CefWidget::paintEvent(QPaintEvent* event) {
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setBrush(Qt::white);
    painter.setPen(Qt::transparent);
    QPainterPath path;
    path.addRoundedRect(rect(), 5, 5);
    painter.fillPath(path, Qt::white);
}

void CefWidget::onInvokeMethod(int browserId, int frameId, const QString& method, const QVariantList& arguments) {
    if (0 == method.compare("change skin")) {
        emit sigSkinChange(arguments.at(0).toString());
    }
    else if (0 == method.compare("fixed to screen")) {
        emit sigFixedToScreen();
        sendCefEvent("fixedToScreen", m_monitorPtr->getFixedValue() ? tr("unfixed to screen") : tr("fixed to screen"));
    }
    else if (0 == method.compare("modify transparency")) {
        int value = arguments.at(0).toInt();
        emit sigTransparencyChange(value);
    }
}

void CefWidget::onQCefQueryRequest(int browserId, int frameId, const QCefQuery& query) {
    if (query.request() == "initFixedToScreenAndTransparency") {
        QString response(m_monitorPtr->getFixedValue() ? tr("unfixed to screen") : tr("fixed to screen"));
        response.append(";");
        response.append(QString::number(99 - m_monitorPtr->getTransparency() * 99));
        query.setResponseResult(true, response);
        m_cefViewWidget->responseQCefQuery(query);
    }
}

void CefWidget::sendCefEvent(const QString& cefEvent, const QVariant& value) {
    if (!m_cefViewWidget)
        return;
    QCefEvent event(cefEvent);
    QVariantList list;
    list.emplace_back(value);
    event.setArguments(list);
    m_cefViewWidget->broadcastEvent(event);
}

右击主界面即可弹出cef界面,包括换肤、是否固定到屏幕及更改主窗透明度三个功能,最终得到cef界面如下:

image.png

前端网页的部分是参考https://blog.csdn.net/cpp_learner/article/details/126052641 来写的,html文件如下:
// trafficMonitor.html
<!doctype html>
<html lang="en">  
    <head>
        <meta charset="UTF-8">
        <title>trafficMonitor</title>
        <link rel="stylesheet" type="text/css" href="trafficMonitor.css"/>
    </head>
    <body onload="onLoad()" id="main">
		<button class="btn warning" onclick="onChangeSkinClicked('change skin')">换肤</button><br/>
		<button id="fixedToScreenBtn" class="btn info" onclick="onInvokeMethodClicked('fixed to screen')">固定到屏幕</button><br/>
		<div class="slidecontainer">
		<input type="range" id="transparencySlider" class="slider" min="0" max="99" step="1" value="0" onchange="transparencyChange()"/><br/>
		</div>
		<script>
			var index = 0;
			var query = {
				request: "initFixedToScreenAndTransparency",
				onSuccess: on_success,
			};
			window.CefViewQuery(query);
			function on_success(response) {
				arr = response.split(";");
				var button = document.getElementById("fixedToScreenBtn");
				button.textContent = arr[0];
				var slider = document.getElementById("transparencySlider");
				slider.value = arr[1];
			}
			function onChangeSkinClicked(name, ...arg) {
				var suffix = "http://xx.xx.xx.xx/bgImage" + index + ".jpg";
				window.CallBridge.invokeMethod(name, suffix);
				++index
				if (index >= 30) {
					index = 0;
				}
			}
			function onInvokeMethodClicked(name, ...arg) {
				window.CallBridge.invokeMethod(name, ...arg);
			}
			function onLoad() {
				CallBridge.addEventListener("fixedToScreen", fixedToScreen);
			}
			function fixedToScreen(flag, ...arg) {
				var button = document.getElementById("fixedToScreenBtn");
				button.textContent = flag;
			}
			function transparencyChange(){
				ran = document.getElementById("transparencySlider");
				window.CallBridge.invokeMethod("modify transparency", ran.value);
			}
        </script>
    </body>
</html>