diff --git a/FunctionalModule/FunctionalModule.assets/image-20220408223533219.png b/FunctionalModule/FunctionalModule.assets/image-20220408223533219.png index 0e9da1146f0ea310524f6c03ad3cbd1ff89ccf47..06bdc8b2d56c35e4eef5bf7ab715070682e79e38 100644 Binary files a/FunctionalModule/FunctionalModule.assets/image-20220408223533219.png and b/FunctionalModule/FunctionalModule.assets/image-20220408223533219.png differ diff --git a/FunctionalModule/FunctionalModule.assets/image-20221121125725058.png b/FunctionalModule/FunctionalModule.assets/image-20221121125725058.png new file mode 100644 index 0000000000000000000000000000000000000000..b7680a3cb77dac805b1d613382ab85cfb14c645b Binary files /dev/null and b/FunctionalModule/FunctionalModule.assets/image-20221121125725058.png differ diff --git a/FunctionalModule/FunctionalModule.assets/log.PNG b/FunctionalModule/FunctionalModule.assets/log.PNG index bf4510b98a3717a7909638e41fabeaf68967a42f..eacd74deded1e495b24ef429709c434868ac2b75 100644 Binary files a/FunctionalModule/FunctionalModule.assets/log.PNG and b/FunctionalModule/FunctionalModule.assets/log.PNG differ diff --git a/FunctionalModule/FunctionalModule.assets/mouseEvent-tuya.gif b/FunctionalModule/FunctionalModule.assets/mouseEvent-tuya.gif new file mode 100644 index 0000000000000000000000000000000000000000..f2f7977606a5131a9600c09355f798cfbc6c802f Binary files /dev/null and b/FunctionalModule/FunctionalModule.assets/mouseEvent-tuya.gif differ diff --git a/FunctionalModule/FunctionalModule.md b/FunctionalModule/FunctionalModule.md index 96727310aee0788d1d51d57a222d13f62d7bcdb9..d701821f4d31a32e059cbfdbe1079b5faf69a957 100644 --- a/FunctionalModule/FunctionalModule.md +++ b/FunctionalModule/FunctionalModule.md @@ -15,6 +15,8 @@ | TestCrashHandler | windows下程序崩溃定位Demo | windows | | NtpClient | NTP时间同步客户端 | Windows、Linux | | WindowRect | 框选鼠标当前位置窗口范围(类似窗口截图) | Windows、Linux | +| MouseKeyEvent | Qt实现自定义全局鼠标键盘事件监听器Demo | Windows | +| SnippingTool | Qt实现截图工具 | Windows、Linux | @@ -131,4 +133,33 @@ * linux -![windowRect2-tuya](FunctionalModule.assets/windowRect2-tuya.gif) \ No newline at end of file +![windowRect2-tuya](FunctionalModule.assets/windowRect2-tuya.gif) + + + +### 1.8 MouseKeyEvent + +Qt自身的鼠标事件、事件过滤器一般当鼠标移出窗口或者遇见鼠标穿透时就不起作用了,这时如果还想要鼠标事件只能自己封装一个全局鼠标事件监听器; + +> 1. windows下使用鼠标钩子实现全局鼠标监听功能; +> 2. 通过封装将Windows鼠标信号转换成Qt鼠标信号; +> 3. 键盘事件(待完成); + +* 全局鼠标监听 + +![mouseEvent-tuya](FunctionalModule.assets/mouseEvent-tuya.gif) + + + +### 1.9 SnippingTool + +* 使用Qt编写的一个截图工具软件,支持Windows、linux系统,无第三方依赖,均使用Qt库或者系统库实现。 + +> 1. 实现Windows、linux系统下截图功能; +> 2. 实现全屏截图、矩形截图、窗口截图功能; +> 3. 实现保存截图、取消截图功能; +> 4. 使用QPainter实时显示截取的图片; +> 5. 使用自定义全局 鼠标事件监听器解决截图时窗口透明导致的鼠标穿透而无法捕捉到鼠标事件问题。 +> 6. Windows下使用user32获取鼠标所在位置窗口大小,Linux下使用x11获取鼠标所在位置窗口大小。 + +![image-20221121125725058](FunctionalModule.assets/image-20221121125725058.png) \ No newline at end of file diff --git a/FunctionalModule/FunctionalModule.pro b/FunctionalModule/FunctionalModule.pro index eead3d94667fb03e921c533b13e68e47aa91a29a..b1bf74afd310b19bd493d31af8724528c87f3097 100644 --- a/FunctionalModule/FunctionalModule.pro +++ b/FunctionalModule/FunctionalModule.pro @@ -10,8 +10,9 @@ #--------------------------------------------------------------------------------------- TEMPLATE = subdirs -SUBDIRS += QMWidget \ # qt自定义窗口 - WindowRect +SUBDIRS += QMWidget # qt自定义窗口 +SUBDIRS += WindowRect # 使用透明窗口框选鼠标所在窗口范围 +SUBDIRS += SnippingTool # Qt实现截图工具 SUBDIRS += DeviceManagement # 串口、鼠标、键盘热插拔检测模块 SUBDIRS += QLog # 自定义日志系统 SUBDIRS += QMPlayer # 视频播放器界面 diff --git a/FunctionalModule/MouseKeyEvent/MouseKeyEvent.pro b/FunctionalModule/MouseKeyEvent/MouseKeyEvent.pro new file mode 100644 index 0000000000000000000000000000000000000000..8932683a9f8ad9cea58ada519cfc144cd8666675 --- /dev/null +++ b/FunctionalModule/MouseKeyEvent/MouseKeyEvent.pro @@ -0,0 +1,51 @@ +#--------------------------------------------------------------------------------------- +# @功能: 全局鼠标、键盘事件示例 +# 1、windows下使用鼠标钩子实现全局鼠标监听功能; +# 2、通过封装将Windows鼠标信号转换成Qt鼠标信号; +# @编译器: Desktop Qt 5.12.5 MSVC2017 64bit(也支持其它编译器) +# @Qt IDE: D:/Qt/Qt5.12.5/Tools/QtCreator/share/qtcreator +# +# @开发者 mhf +# @邮箱 1603291350@qq.com +# @时间 2022-11-13 22:23:08 +# @备注 +#--------------------------------------------------------------------------------------- +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +CONFIG += c++11 +DEFINES += QT_DEPRECATED_WARNINGS +SOURCES += \ + main.cpp \ + widget.cpp + +HEADERS += \ + widget.h + +FORMS += \ + widget.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +# 定义程序版本号 +VERSION = 1.0.0 +DEFINES += APP_VERSION=\\\"$$VERSION\\\" + +contains(QT_ARCH, i386){ # 使用32位编译器 +DESTDIR = $$PWD/../bin # 程序输出路径 +}else{ +DESTDIR = $$PWD/../bin64 # 使用64位编译器 +} +# msvc 编译器使用utf-8编码 +msvc { +QMAKE_CFLAGS += /utf-8 +QMAKE_CXXFLAGS += /utf-8 +} + +win32 { +LIBS+= -luser32 # 使用WindowsAPI需要链接库 +} + diff --git a/FunctionalModule/MouseKeyEvent/main.cpp b/FunctionalModule/MouseKeyEvent/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b0a4ec26478f6b9aba3e1747ec464ea0c26dd5b9 --- /dev/null +++ b/FunctionalModule/MouseKeyEvent/main.cpp @@ -0,0 +1,11 @@ +#include "widget.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + Widget w; + w.show(); + return a.exec(); +} diff --git a/FunctionalModule/MouseKeyEvent/widget.cpp b/FunctionalModule/MouseKeyEvent/widget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9bbe35af2156e5b27e4140db8a4ea87d141c368b --- /dev/null +++ b/FunctionalModule/MouseKeyEvent/widget.cpp @@ -0,0 +1,184 @@ +#include "widget.h" +#include "ui_widget.h" +#include + +#if defined(Q_OS_WIN) +#include "Windows.h" +#endif + +Widget::Widget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::Widget) +{ + ui->setupUi(this); + + this->setWindowTitle(QString("Qt-自定义全局鼠标监听Demo - V%1").arg(APP_VERSION)); + connect(MouseEvent::getInstance(), &MouseEvent::mouseSignal, this, &Widget::on_mouseSignal); +} + +Widget::~Widget() +{ + delete ui; +} + + +void Widget::on_mouseSignal(QEvent* event) +{ + QMouseEvent* me = dynamic_cast(event); + switch (event->type()) + { + case QEvent::MouseButtonPress: // 鼠标按下 + { + QString but; + switch (me->button()) + { + case Qt::LeftButton: + { + but = "左键"; + break; + } + case Qt::RightButton: + { + but = "右键"; + break; + } + default: + { + but = "未知"; + break; + } + } + QString str = QString("鼠标%1按下:(x:%2, y:%3)").arg(but).arg(me->x()).arg(me->y()); + ui->textEdit->append(str); + break; + } + case QEvent::MouseMove: // 鼠标移动 + { + QString str = QString("鼠标移动:(x:%1, y:%2)").arg(me->x()).arg(me->y()); + ui->textEdit->append(str); + break; + } + case QEvent::MouseButtonRelease: // 鼠标右键抬起 + { + QString but; + switch (me->button()) + { + case Qt::LeftButton: + { + but = "左键"; + break; + } + case Qt::RightButton: + { + but = "右键"; + break; + } + default: + { + but = "未知"; + break; + } + } + QString str = QString("鼠标%1释放:(x:%2, y:%3)").arg(but).arg(me->x()).arg(me->y()); + ui->textEdit->append(str); + break; + } + case QEvent::Wheel: // 鼠标滚轮 + { + QWheelEvent* we = dynamic_cast(event); + QString str = QString("鼠标滚轮:%1,(x:%2, y:%3)").arg(we->delta() > 0 ? "向前" : "向后").arg(we->x()).arg(we->y()); + ui->textEdit->append(str); + break; + } + default: + break; + } + + delete event; +} + +#if defined(Q_OS_WIN) +/** + * @brief 处理鼠标事件的回调函数,由于这不是一个成员函数,所以需要通过中间单例类mouseEvent将鼠标信号传递出来 + * 具体内容看https://learn.microsoft.com/zh-cn/previous-versions/windows/desktop/legacy/ms644986(v=vs.85) + * @param nCode 挂钩过程用于确定如何处理消息的代码。如果nCode小于零,则挂钩过程必须将消息传递给 CallNextHookEx 函数而不进行进一步处理,并且应返回CallNextHookEx返回的值 + * @param wParam 信号类型:WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE、WM_MOUSEWHEEL、WM_MOUSEHWHEEL、WM_RBUTTONDOWN 或WM_RBUTTONUP。 + * @param lParam MSLLHOOKSTRUCT结构体指针 + * @return + */ +LRESULT CALLBACK MouseCallback(int nCode, WPARAM wParam, LPARAM lParam) +{ + QPoint point = QCursor::pos(); // 获取鼠标当前位置 + switch (wParam) + { + case WM_LBUTTONDOWN: // 鼠标左键按下 + emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); + break; + case WM_MOUSEMOVE: // 鼠标移动 + emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseMove, point, Qt::NoButton, Qt::NoButton, Qt::NoModifier)); + break; + case WM_RBUTTONDOWN: // 鼠标右键按下 + emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonPress, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier)); + break; + case WM_RBUTTONUP: // 鼠标右键抬起 + emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); + break; + case WM_LBUTTONUP: // 鼠标左键抬起 + emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonRelease, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier)); + break; + case WM_MOUSEWHEEL: // 鼠标滚轮 + { + MSLLHOOKSTRUCT * msll = reinterpret_cast(lParam); +// qDebug() << QString("坐标:(%1, %2)").arg(msll->pt.x).arg(msll->pt.y); // 获取鼠标坐标 + int delta = GET_WHEEL_DELTA_WPARAM(msll->mouseData); // 获取滚轮状态,向前:120,向后-120 + emit MouseEvent::getInstance()->mouseSignal(new QWheelEvent(point, delta, Qt::MiddleButton, Qt::NoModifier)); + break; + } + default: + break; + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); // 注意这一行一定不能少,否则会出大问题 +} + +static HHOOK g_hook = nullptr; +#endif +/** + * @brief 安装全局鼠标钩子 + */ +void Widget::on_but_mouseI_clicked() +{ +#if defined(Q_OS_WIN) + if(g_hook) return; // 避免重复安装 + /** + * WH_KEYBOARD_LL 为全局键盘钩子, WH_MOUSE_LL 为全局鼠标钩子 + * 详细说明看官方文档:https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-setwindowshookexw + */ + g_hook = SetWindowsHookExW(WH_MOUSE_LL, MouseCallback, GetModuleHandleW(nullptr), 0); + if (g_hook) + { + qDebug() << "鼠标钩子挂接成功,线程ID:" << GetCurrentThreadId(); + + } + else + { + qDebug() << "鼠标钩子挂接失败:" << GetLastError(); + } +#endif +} + +/** + * @brief 卸载鼠标钩子 + */ +void Widget::on_but_mouser_clicked() +{ +#if defined(Q_OS_WIN) + if(!g_hook) return; // 避免重复卸载 + bool ret = UnhookWindowsHookEx(g_hook); + if(ret) + { + g_hook = nullptr; + qDebug() << "卸载鼠标钩子。"; + } +#endif +} diff --git a/FunctionalModule/MouseKeyEvent/widget.h b/FunctionalModule/MouseKeyEvent/widget.h new file mode 100644 index 0000000000000000000000000000000000000000..1f494f79857a6de74a271133d7602e9de0efc785 --- /dev/null +++ b/FunctionalModule/MouseKeyEvent/widget.h @@ -0,0 +1,48 @@ +#ifndef WIDGET_H +#define WIDGET_H + +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class Widget; } +QT_END_NAMESPACE + +class Widget : public QWidget +{ + Q_OBJECT +public: + Widget(QWidget *parent = nullptr); + ~Widget(); + +private slots: + void on_mouseSignal(QEvent* event); + void on_but_mouseI_clicked(); + + void on_but_mouser_clicked(); + +private: + Ui::Widget *ui; +}; + +/** + * 全局鼠标事件单例信号类 + */ +class MouseEvent : public QObject +{ + Q_OBJECT +public: + static MouseEvent* getInstance() + { + static MouseEvent mouseEvent; + return &mouseEvent; + } + +signals: + void mouseSignal(QEvent* event); + +private: + MouseEvent(){} +}; + +#endif // WIDGET_H diff --git a/FunctionalModule/MouseKeyEvent/widget.ui b/FunctionalModule/MouseKeyEvent/widget.ui new file mode 100644 index 0000000000000000000000000000000000000000..86f08b035f385df25e96d0c063818fe828903315 --- /dev/null +++ b/FunctionalModule/MouseKeyEvent/widget.ui @@ -0,0 +1,57 @@ + + + Widget + + + + 0 + 0 + 581 + 505 + + + + + 宋体 + 12 + + + + Widget + + + + + + 卸载鼠标钩子 + + + + + + + + + + 安装鼠标钩子 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/FunctionalModule/SnippingTool/SnippingTool.pro b/FunctionalModule/SnippingTool/SnippingTool.pro new file mode 100644 index 0000000000000000000000000000000000000000..7551d3ef36208bc7eb2b050419f9ff80f0ac03ed --- /dev/null +++ b/FunctionalModule/SnippingTool/SnippingTool.pro @@ -0,0 +1,65 @@ +#--------------------------------------------------------------------------------------- +# @功能: Qt实现截图工具 +# 1、实现Windows、linux系统下截图功能; +# 2、实现全屏截图、矩形截图、窗口截图功能; +# 3、实现保存截图、取消截图功能; +# 4、使用QPainter实时显示截取的图片。 +# @编译器: Desktop Qt 5.12.5 MSVC2017 64bit(也支持其它编译器) +# @Qt IDE: D:/Qt/Qt5.12.5/Tools/QtCreator/share/qtcreator +# +# @开发者 mhf +# @邮箱 1603291350@qq.com +# @时间 2022-11-16 14:36:27 +# @备注 +#--------------------------------------------------------------------------------------- +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets +CONFIG += c++11 +DEFINES += QT_DEPRECATED_WARNINGS + +FORMS += \ + mainwindow.ui + +HEADERS += \ + mainwindow.h \ + playimage.h \ + screenrect.h \ + windowrect.h + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + playimage.cpp \ + screenrect.cpp \ + windowrect.cpp + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +# 定义程序版本号 +VERSION = 1.1.0 +DEFINES += APP_VERSION=\\\"$$VERSION\\\" +RC_ICONS = icon.ico # 设置程序图标 + +contains(QT_ARCH, i386){ # 使用32位编译器 +DESTDIR = $$PWD/../bin # 程序输出路径 +}else{ +DESTDIR = $$PWD/../bin64 # 使用64位编译器 +} +# msvc 编译器使用utf-8编码 +msvc { +QMAKE_CFLAGS += /utf-8 +QMAKE_CXXFLAGS += /utf-8 +} + +win32 { +LIBS+= -luser32 # 使用WindowsAPI需要链接库 +} +unix:!macx{ +LIBS += -lX11 # linux获取窗口信息需要用到xlib +} + +RESOURCES += rc.qrc diff --git a/FunctionalModule/SnippingTool/icon.ico b/FunctionalModule/SnippingTool/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b8f744187bc8a7f709c35bf2a8a60af4dbd6f2c5 Binary files /dev/null and b/FunctionalModule/SnippingTool/icon.ico differ diff --git "a/FunctionalModule/SnippingTool/img/\344\277\235\345\255\230.ico" "b/FunctionalModule/SnippingTool/img/\344\277\235\345\255\230.ico" new file mode 100644 index 0000000000000000000000000000000000000000..21fc59b6466caf618caeb77bb24c8d0f78cca689 Binary files /dev/null and "b/FunctionalModule/SnippingTool/img/\344\277\235\345\255\230.ico" differ diff --git "a/FunctionalModule/SnippingTool/img/\345\211\252\345\210\207.ico" "b/FunctionalModule/SnippingTool/img/\345\211\252\345\210\207.ico" new file mode 100644 index 0000000000000000000000000000000000000000..b8f744187bc8a7f709c35bf2a8a60af4dbd6f2c5 Binary files /dev/null and "b/FunctionalModule/SnippingTool/img/\345\211\252\345\210\207.ico" differ diff --git "a/FunctionalModule/SnippingTool/img/\345\217\226\346\266\210.ico" "b/FunctionalModule/SnippingTool/img/\345\217\226\346\266\210.ico" new file mode 100644 index 0000000000000000000000000000000000000000..c5e302323364c93bf71321c63b5808c430e84058 Binary files /dev/null and "b/FunctionalModule/SnippingTool/img/\345\217\226\346\266\210.ico" differ diff --git "a/FunctionalModule/SnippingTool/img/\351\200\211\345\214\272.ico" "b/FunctionalModule/SnippingTool/img/\351\200\211\345\214\272.ico" new file mode 100644 index 0000000000000000000000000000000000000000..b77c71dfec8375feb79c0dee5118db3316942b1f Binary files /dev/null and "b/FunctionalModule/SnippingTool/img/\351\200\211\345\214\272.ico" differ diff --git a/FunctionalModule/SnippingTool/main.cpp b/FunctionalModule/SnippingTool/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fd3e533415011b7a24814fe7f3ac990d6a811132 --- /dev/null +++ b/FunctionalModule/SnippingTool/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/FunctionalModule/SnippingTool/mainwindow.cpp b/FunctionalModule/SnippingTool/mainwindow.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f009e3e649207c5f5001b62eee7013aa0ae8e35a --- /dev/null +++ b/FunctionalModule/SnippingTool/mainwindow.cpp @@ -0,0 +1,155 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + this->setWindowTitle(QString("Qt-截图工具 - V%1").arg(APP_VERSION)); + + // 设置工具栏 + QAction* acNew = new QAction(QIcon(":/img/剪切.ico"), "新建截图"); + QAction* acSave = new QAction(QIcon(":/img/保存.ico"), "保存截图"); + QAction* acClear = new QAction(QIcon(":/img/取消.ico"), "取消截图"); + m_acModel = new QAction(QIcon(":/img/选区.ico"), "截图模式"); + QMenu* menu = new QMenu(this); + menu->addAction(new QAction("全屏", this)); + menu->addAction(new QAction("矩形", this)); + menu->addAction(new QAction("窗口", this)); + QToolBar* toolbar = new QToolBar(this); + m_acModel->setMenu(menu); + toolbar->addAction(acNew); + toolbar->addAction(m_acModel); + toolbar->addAction(acSave); + toolbar->addAction(acClear); + toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + this->addToolBar(Qt::TopToolBarArea, toolbar); // 添加工具栏 + connect(menu, &QMenu::triggered, this, &MainWindow::on_triggered); + connect(acNew, &QAction::triggered, this, &MainWindow::on_newGrab); + connect(acSave, &QAction::triggered, this, &MainWindow::on_saveImage); + connect(acClear, &QAction::triggered, this, &MainWindow::on_clearImage); + connect(&m_screenRect, &ScreenRect::selectRect, this, &MainWindow::on_selectRect); + connect(&m_windowRect, &WindowRect::selectRect, this, &MainWindow::on_selectRect); + +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +/** + * @brief 更新当前选择模式 + * @param action + */ +void MainWindow::on_triggered(QAction *action) +{ + m_acModel->setText(action->text()); +} + +void MainWindow::on_newGrab(bool checked) +{ + Q_UNUSED(checked) + QString strModel = m_acModel->text(); + if(strModel == "全屏") + { + grabPixmap(QRect(0, 0, -1, -1)); + } + else if(strModel == "矩形") + { + this->hide(); + m_screenRect.show(); + } + else if(strModel == "窗口") + { + this->hide(); + m_windowRect.show(); + } + else + { + } +} + +/** + * @brief 开始截图 + * @param rect + */ +void MainWindow::grabPixmap(QRect rect) +{ +#if defined(Q_OS_WIN) + setWindowOpacity(0); // 最好的方法是将当前窗口设置成完全透明 + QDesktopWidget *desk = QApplication::desktop(); // 获取桌面根窗口 + QScreen * screen = QGuiApplication::primaryScreen(); // 获取默认屏幕 + m_pixmap = screen->grabWindow(desk->winId(), rect.x(), rect.y(), rect.width(), rect.height()); // 抓取屏幕图像 + ui->centralwidget->updatePixmap(m_pixmap); // 显示捕获的图像 + setWindowOpacity(1); +#elif defined(Q_OS_LINUX) + // linux下setWindowOpacity设置透明后截图还可以看到一个透明的边框,效果不是很好,所以使用hide + this->hide(); // 截图之前将当前窗口隐藏,避免截取的图像中包含当前窗口,这种方法很慢,需要延时等待几百毫秒,否则还是会有当前窗口 + QThread::msleep(300); + QDesktopWidget *desk = QApplication::desktop(); // 获取桌面根窗口 + QScreen * screen = QGuiApplication::primaryScreen(); // 获取默认屏幕 + m_pixmap = screen->grabWindow(desk->winId(), rect.x(), rect.y(), rect.width(), rect.height()); // 抓取屏幕图像 + ui->centralwidget->updatePixmap(m_pixmap); // 显示捕获的图像 + this->show(); // 截图完成后显示窗口 +#endif +} + +/** + * @brief 保存截图 + * @param checked + */ +void MainWindow::on_saveImage(bool checked) +{ + Q_UNUSED(checked) + if(m_pixmap.isNull()) return; + + // linux下getSaveFileName不会返回默认文件后缀,所以需要在文件名中添加后缀,否则QImage::save无法通过后缀推测出文件类型,就会保存失败 + QString name = QString("%1.png").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh-mm-ss")); + QString strName = QFileDialog::getSaveFileName(this, "保存到", name, "便携式网络图形(*.png);;JPEG文件(*.jpg)"); + if(strName.isEmpty()) return; + + QImage image = m_pixmap.toImage(); + if(image.save(strName)) + { + qDebug() << "保存成功!"; + } + else + { + QMessageBox::warning(this, "注意!", "文件保存失败,请检查有没有输入文件后缀名。"); + } +} + +/** + * @brief 清除截图 + * @param checked + */ +void MainWindow::on_clearImage(bool checked) +{ + Q_UNUSED(checked) + if(m_pixmap.isNull()) return; + m_pixmap = QPixmap(); + ui->centralwidget->updatePixmap(m_pixmap); +} + +/** + * @brief 矩形选择截图 + * @param rect + */ +void MainWindow::on_selectRect(QRect rect) +{ + this->show(); + grabPixmap(rect); +} diff --git a/FunctionalModule/SnippingTool/mainwindow.h b/FunctionalModule/SnippingTool/mainwindow.h new file mode 100644 index 0000000000000000000000000000000000000000..6c7d81b4c070318c9001e84fec64f8107d2a91b1 --- /dev/null +++ b/FunctionalModule/SnippingTool/mainwindow.h @@ -0,0 +1,36 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "screenrect.h" +#include "windowrect.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + void on_triggered(QAction *action); + void on_newGrab(bool checked = false); + void on_saveImage(bool checked = false); + void on_clearImage(bool checked = false); + void on_selectRect(QRect rect); + void grabPixmap(QRect rect); // 捕获图像 + +private: + Ui::MainWindow *ui; + QAction* m_acModel = nullptr; + QPixmap m_pixmap; // 保存截取的图像 + ScreenRect m_screenRect; + WindowRect m_windowRect; +}; +#endif // MAINWINDOW_H diff --git a/FunctionalModule/SnippingTool/mainwindow.ui b/FunctionalModule/SnippingTool/mainwindow.ui new file mode 100644 index 0000000000000000000000000000000000000000..f318bfa8783a9ddf58c0077bbe52bd71ca2deb21 --- /dev/null +++ b/FunctionalModule/SnippingTool/mainwindow.ui @@ -0,0 +1,46 @@ + + + MainWindow + + + + 0 + 0 + 549 + 287 + + + + MainWindow + + + /********************QToolBar样式**********************/ +QToolButton { + width: 100px; + height: 30px; +} + + + + + + + 0 + 0 + 549 + 23 + + + + + + + PlayImage + QWidget +
playimage.h
+ 1 +
+
+ + +
diff --git a/FunctionalModule/SnippingTool/playimage.cpp b/FunctionalModule/SnippingTool/playimage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..197d16e4e77d6620eeb99b5e846dafc3d0d7860e --- /dev/null +++ b/FunctionalModule/SnippingTool/playimage.cpp @@ -0,0 +1,59 @@ +#include "playimage.h" + +#include + +PlayImage::PlayImage(QWidget *parent) : QWidget(parent) +{ + // 适用调色板设置背景色 + QPalette palette(this->palette()); + palette.setColor(QPalette::Background, Qt::black); //设置背景黑色 + this->setPalette(palette); + this->setAutoFillBackground(true); +} + +/** + * @brief 传入Qimage图片显示 + * @param image + */ +void PlayImage::updateImage(const QImage& image) +{ + updatePixmap(QPixmap::fromImage(image)); +} + +/** + * @brief 传入QPixmap图片 + * @param pixmap + */ +void PlayImage::updatePixmap(const QPixmap &pixmap) +{ + m_mutex.lock(); + m_pixmap = pixmap; + m_mutex.unlock(); + update(); +} + +/** + * @brief 使用Qpainter显示图片 + * @param event + */ +void PlayImage::paintEvent(QPaintEvent *event) +{ + if(!m_pixmap.isNull()) + { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); +#if 0 + // 经过粗略测试,QImage先缩放后转为QPixmap的方式在图像比较小时耗时少,图片越大耗时越大 + QPixmap pixmap = QPixmap::fromImage(m_image.scaled(this->size(), Qt::KeepAspectRatio)); + // 先将QImage转换为QPixmap再进行缩放则耗时比较少,并且稳定,不会因为缩放图片大小而产生太大影响 + QPixmap pixmap1 = QPixmap::fromImage(m_image).scaled(this->size(), Qt::KeepAspectRatio); +#endif + m_mutex.lock(); + QPixmap pixmap = m_pixmap.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); // 这里采用SmoothTransformation,保证显示图像的清晰度 + m_mutex.unlock(); + int x = (this->width() - pixmap.width()) / 2; + int y = (this->height() - pixmap.height()) / 2; + painter.drawPixmap(x, y, pixmap); + } + QWidget::paintEvent(event); +} diff --git a/FunctionalModule/SnippingTool/playimage.h b/FunctionalModule/SnippingTool/playimage.h new file mode 100644 index 0000000000000000000000000000000000000000..42792c06dddaef93b03d98774d7acc26457f7ca2 --- /dev/null +++ b/FunctionalModule/SnippingTool/playimage.h @@ -0,0 +1,26 @@ +#ifndef PLAYIMAGE_H +#define PLAYIMAGE_H + +#include +#include + +class PlayImage : public QWidget +{ + Q_OBJECT +public: + explicit PlayImage(QWidget *parent = nullptr); + + void updateImage(const QImage& image); + void updatePixmap(const QPixmap& pixmap); + +signals: + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QPixmap m_pixmap; + QMutex m_mutex; +}; + +#endif // PLAYIMAGE_H diff --git a/FunctionalModule/SnippingTool/rc.qrc b/FunctionalModule/SnippingTool/rc.qrc new file mode 100644 index 0000000000000000000000000000000000000000..297dc04a03489a78aa37f8f70662163b99898ef5 --- /dev/null +++ b/FunctionalModule/SnippingTool/rc.qrc @@ -0,0 +1,8 @@ + + + img/保存.ico + img/剪切.ico + img/取消.ico + img/选区.ico + + diff --git a/FunctionalModule/SnippingTool/screenrect.cpp b/FunctionalModule/SnippingTool/screenrect.cpp new file mode 100644 index 0000000000000000000000000000000000000000..86b28964a790fb2a8f0bf1e0ac2e2ab972199858 --- /dev/null +++ b/FunctionalModule/SnippingTool/screenrect.cpp @@ -0,0 +1,76 @@ +#include "screenrect.h" + +#include +#include +#include +#include +#include +#include + +ScreenRect::ScreenRect(QWidget *parent) : QWidget(parent) +{ + this->setWindowFlag(Qt::FramelessWindowHint); // 去除标题栏 + this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明 + QScreen * screen = QGuiApplication::primaryScreen(); // 获取默认屏幕 + m_path.addRect(screen->geometry()); + this->setGeometry(screen->geometry()); +} + +/** + * @brief 鼠标选择的启点 + * @param event + */ +void ScreenRect::mousePressEvent(QMouseEvent *event) +{ + if(event->button() == Qt::LeftButton) + { + m_rect.setTopLeft(event->pos()); + } + QWidget::mousePressEvent(event); +} + +/** + * @brief 鼠标释放时如果选择了区域则将选择的矩形发送出去 + * @param event + */ +void ScreenRect::mouseReleaseEvent(QMouseEvent *event) +{ + if((event->button() == Qt::LeftButton) && (m_rect.x() > 0 || m_rect.y() > 0)) // 这里使用x >0,y>0判断是否选择区域,而不是使用!m_rect.isEmpty()判断,避免反着选 + { + this->hide(); + emit this->selectRect(m_rect.normalized()); // 使用normalized防止出现反着选,例如从下往上选,从右往左选 + m_rect = QRect(); // 置为空 + } + + QWidget::mouseReleaseEvent(event); +} + +/** + * @brief 鼠标选择的终点 + * @param event + */ +void ScreenRect::mouseMoveEvent(QMouseEvent *event) +{ + if(m_rect.x() > 0 || m_rect.y() > 0) + { + m_rect.setBottomRight(event->pos()); + this->update(); + } + QWidget::mouseMoveEvent(event); +} + +/** + * @brief 绘制全屏遮罩和选择的矩形框 + * @param event + */ +void ScreenRect::paintEvent(QPaintEvent *event) +{ + QWidget::paintEvent(event); + + QPainter painter(this); + painter.setPen(QPen(QColor(85, 170, 255, 200), 1)); + painter.setBrush(QColor(85, 170, 255, 50)); + QPainterPath path; + path.addRect(m_rect); + painter.drawPath(m_path - path); // 绘制选择的矩形框 +} diff --git a/FunctionalModule/SnippingTool/screenrect.h b/FunctionalModule/SnippingTool/screenrect.h new file mode 100644 index 0000000000000000000000000000000000000000..df5ab9564c8b2ccd523dd6aef1e33a0d0a157550 --- /dev/null +++ b/FunctionalModule/SnippingTool/screenrect.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * @文件名 screenrect.h + * @功能 矩形截图选框窗口 + * + * @开发者 mhf + * @邮箱 1603291350@qq.com + * @时间 2022/11/18 + * @备注 + *****************************************************************************/ +#ifndef SCREENRECT_H +#define SCREENRECT_H + +#include + +class ScreenRect : public QWidget +{ + Q_OBJECT +public: + explicit ScreenRect(QWidget *parent = nullptr); + +signals: + void selectRect(QRect rect); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + QRect m_rect; // 选择的矩形 + QPainterPath m_path; +}; + +#endif // SCREENRECT_H diff --git a/FunctionalModule/SnippingTool/windowrect.cpp b/FunctionalModule/SnippingTool/windowrect.cpp new file mode 100644 index 0000000000000000000000000000000000000000..556ba77328408b85586827cbede47baae11f569e --- /dev/null +++ b/FunctionalModule/SnippingTool/windowrect.cpp @@ -0,0 +1,216 @@ +#include "windowrect.h" +#include +#include +#include +#include + +#if defined(Q_OS_WIN) +#include +#include +#elif defined(Q_OS_LINUX) +#include +#include +#elif defined(Q_OS_MAC) +#endif + +#if defined(Q_OS_WIN) +static HHOOK g_hook = nullptr; +/** + * @brief 处理鼠标事件的回调函数 + * @param nCode + * @param wParam + * @param lParam + * @return + */ +LRESULT CALLBACK CallBackProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + switch (wParam) + { + case WM_LBUTTONDOWN: // 鼠标左键按下 + { + emit MouseEvent::getInstance()->mouseSignal(new QMouseEvent(QEvent::MouseButtonPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); + break; + } + default: + break; + } + return CallNextHookEx(nullptr, nCode, wParam, lParam); // 注意这一行一定不能少,否则会出大问题 +} +#endif + +WindowRect::WindowRect(QWidget *parent) : QWidget(parent) +{ +#if defined(Q_OS_WIN) + // linux下鼠标穿透要放在后面两行代码的全前面,否则无效(但是鼠标穿透了会导致一些奇怪的问题,如窗口显示不全,所以这里不使用) + // windows下如果不设置鼠标穿透则只能捕获到当前窗口 + this->setAttribute(Qt::WA_TransparentForMouseEvents, true); +#endif + this->setWindowFlags(Qt::FramelessWindowHint); // 去掉边框、标题栏 + this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明 + this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint); // 设置顶级窗口,防止遮挡 + + // 在当前窗口上增加一层QWidget,否则不会显示边框 + QGridLayout* gridLayout = new QGridLayout(this); + gridLayout->setSpacing(0); + gridLayout->setContentsMargins(0, 0, 0, 0); + gridLayout->addWidget(new QWidget(), 0, 0, 1, 1); + + this->setStyleSheet(" background-color: rgba(58, 196, 255, 40); border: 2px solid rgba(58, 196, 255, 200);"); // 设置窗口边框样式 dashed虚线,solid 实线 + + connect(MouseEvent::getInstance(), &MouseEvent::mouseSignal, this, &WindowRect::on_mouseSignal); + // 使用定时器定时获取当前鼠标位置的窗口位置信息 + connect(&m_timer, &QTimer::timeout, this, &WindowRect::on_timeout); + m_timer.start(200); +} + +WindowRect::~WindowRect() +{ +#if defined(Q_OS_WIN) + if(g_hook) + { + bool ret = UnhookWindowsHookEx(g_hook); + if(ret) + { + qDebug() << "卸载鼠标钩子。"; + } + } +#endif +} + +/** + * @brief 通过截图全局鼠标事件将当前窗口大小发生出去 + * @param event + */ +void WindowRect::on_mouseSignal(QEvent *event) +{ + delete event; + this->hide(); + emit this->selectRect(QRect(this->pos(), this->size())); +} + +/** + * @brief Windows使用全局鼠标钩子,显示窗口时挂载鼠标钩子 + * @param event + */ +void WindowRect::showEvent(QShowEvent *event) +{ +#if defined(Q_OS_WIN) + // 由于windows不透明的窗体如果不设置设置鼠标穿透WindowFromPoint只能捕捉到当前窗体,而设置鼠标穿透后想要获取鼠标事件只能通过鼠标钩子 + g_hook = SetWindowsHookExW(WH_MOUSE_LL, CallBackProc, GetModuleHandleW(nullptr), 0); // 挂载全局鼠标钩子 + if (g_hook) + { + qDebug() << "鼠标钩子挂接成功,线程ID:" << GetCurrentThreadId(); + + } + else + { + qDebug() << "鼠标钩子挂接失败:" << GetLastError(); + } +#endif + QWidget::showEvent(event); +} + +/** + * @brief 隐藏窗口时卸载鼠标钩子 + * @param event + */ +void WindowRect::hideEvent(QHideEvent *event) +{ +#if defined(Q_OS_WIN) + if(g_hook) + { + bool ret = UnhookWindowsHookEx(g_hook); + if(ret) + { + qDebug() << "卸载鼠标钩子。"; + g_hook = nullptr; + } + } +#endif + QWidget::hideEvent(event); +} + +/** + * @brief linux下使用自带的鼠标点击事件就可以 + * @param event + */ +void WindowRect::mousePressEvent(QMouseEvent *event) +{ +#if defined(Q_OS_LINUX) + this->hide(); + emit this->selectRect(QRect(this->pos(), this->size())); +#endif + QWidget::mousePressEvent(event); +} + + +void WindowRect::on_timeout() +{ + QPoint point = QCursor::pos(); // 获取鼠标当前位置 +#if defined(Q_OS_WIN) + POINT pos; + pos.x = point.x(); + pos.y = point.y(); + + HWND hwnd = nullptr; + hwnd = WindowFromPoint(pos); // 通过鼠标位置获取窗口句柄 + if(!hwnd) return; + + RECT lrect; + bool ret = GetWindowRect(hwnd, &lrect); //获取窗口位置 + if(!ret) return; + + QRect rect; + rect.setX(lrect.left); + rect.setY(lrect.top); + rect.setWidth(lrect.right - lrect.left); + rect.setHeight(lrect.bottom - lrect.top); + this->setGeometry(rect); // 设置窗口边框 +#elif defined(Q_OS_LINUX) // linux下使用x11获取的窗口大小有可能不太准确,例如浏览器的大小会偏小 + // 获取根窗口 + Display* display = XOpenDisplay(nullptr); + Window rootWindow = DefaultRootWindow(display); + + Window root_return, parent_return; + Window * children = nullptr; + unsigned int nchildren = 0; + // 函数详细说明见xlib文档:https://tronche.com/gui/x/xlib/window-information/XQueryTree.html + // 该函数会返回父窗口的子窗口列表children,因为这里用的是当前桌面的根窗口作为父窗口,所以会返回所有子窗口 + // 注意:窗口顺序(z-order)为自底向上 + XQueryTree(display, rootWindow, &root_return, &parent_return, &children, &nchildren); + + QRect recte; // 保存鼠标当前所在窗口的范围 + for(unsigned int i = 0; i < nchildren; ++i) + { + if(children[i] == this->winId()) continue; // 由于当前窗口一直在最顶层,所以这里要过滤掉当前窗口,否则一直获取到的就是当前窗口大小 + XWindowAttributes attrs; + XGetWindowAttributes(display, children[i], &attrs); // 获取窗口参数 + if (attrs.map_state == IsViewable) // 只处理可见的窗口, 三个状态:IsUnmapped, IsUnviewable, IsViewable + { +#if 0 + QRect rect(attrs.x + 1, attrs.y, attrs.width, attrs.height); // 这里x+1防止全屏显示,如果不+1,设置的大小等于屏幕大小是会自动切换成全屏显示状态,后面就无法缩小了 +#else + QRect rect(attrs.x, attrs.y, attrs.width, attrs.height); +#endif + if(rect.contains(point)) // 判断鼠标坐标是否在窗口范围内 + { + recte = rect; // 记录最后一个窗口的范围 + } + } + } +#if 0 // 在linux下使用setGeometry设置窗口会有一些问题 + this->showNormal(); // 第一次显示是如果是屏幕大小,则后面无法缩小,这里需要设置还原 + this->setGeometry(recte); // 设置窗口边框 +#else // 使用setFixedSize+move可以避免这些问题 + this->move(recte.x(), recte.y()); + this->setFixedSize(recte.width(), recte.height()); +#endif +// qDebug() << this->rect() <windowState(); + + // 注意释放资源 + XFree(children); + XCloseDisplay(display); + +#elif defined(Q_OS_MAC) +#endif +} diff --git a/FunctionalModule/SnippingTool/windowrect.h b/FunctionalModule/SnippingTool/windowrect.h new file mode 100644 index 0000000000000000000000000000000000000000..d2e6b958c0e18d4155ab41f789c7bf390f09e8e2 --- /dev/null +++ b/FunctionalModule/SnippingTool/windowrect.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * @文件名 windowrect.h + * @功能 选中鼠标当前位置窗口的类 + * + * @开发者 mhf + * @邮箱 1603291350@qq.com + * @时间 2022/11/19 + * @备注 + *****************************************************************************/ +#ifndef WINDOWRECT_H +#define WINDOWRECT_H + +#include +#include + +class WindowRect : public QWidget +{ + Q_OBJECT +public: + explicit WindowRect(QWidget *parent = nullptr); + ~WindowRect() override; + +signals: + void selectRect(QRect rect); + +private slots: + void on_mouseSignal(QEvent* event); + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +protected: + void on_timeout(); +private: + QTimer m_timer; +}; + +/** + * 全局鼠标事件单例信号类 + */ +class MouseEvent : public QObject +{ + Q_OBJECT +public: + static MouseEvent* getInstance() + { + static MouseEvent mouseEvent; + return &mouseEvent; + } + +signals: + void mouseSignal(QEvent* event); + +private: + MouseEvent(){} +}; + +#endif // WINDOWRECT_H diff --git a/FunctionalModule/WindowRect/WindowRect.pro b/FunctionalModule/WindowRect/WindowRect.pro index 9ea2481fdb93fd4c520378d7cefabb35c54a0c9b..09f3514a6358bfeab92fd7d6788ceac2a8ad4ca2 100644 --- a/FunctionalModule/WindowRect/WindowRect.pro +++ b/FunctionalModule/WindowRect/WindowRect.pro @@ -1,7 +1,8 @@ #--------------------------------------------------------------------------------------- # @功能: 框选鼠标当前位置窗口范围(类似窗口截图) # 1.使用WindowsAPI实现windows下功能; -# 2.使用x11 API实现linux(ubuntu)下功能。 +# 2.使用x11 API实现linux(ubuntu)下功能; +# 3.windows下使用鼠标钩子解决鼠标穿透后无法获取鼠标点击事件问题。 # @编译器: Desktop Qt 5.12.5 MSVC2017 64bit(也支持其它编译器) # @Qt IDE: D:/Qt/Qt5.12.5/Tools/QtCreator/share/qtcreator # @@ -28,7 +29,7 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target # 定义程序版本号 -VERSION = 1.1.0 +VERSION = 1.2.0 DEFINES += APP_VERSION=\\\"$$VERSION\\\" contains(QT_ARCH, i386){ # 使用32位编译器 diff --git a/FunctionalModule/WindowRect/main.cpp b/FunctionalModule/WindowRect/main.cpp index b0a4ec26478f6b9aba3e1747ec464ea0c26dd5b9..91d8b1328720517fe724fc817bafd64040e37a09 100644 --- a/FunctionalModule/WindowRect/main.cpp +++ b/FunctionalModule/WindowRect/main.cpp @@ -1,5 +1,4 @@ #include "widget.h" - #include int main(int argc, char *argv[]) diff --git a/FunctionalModule/WindowRect/widget.cpp b/FunctionalModule/WindowRect/widget.cpp index db1fb23edc2527f1c3bbae67d1482576d4893deb..51f30846c20c67d938fea1c974c9af43124eb7bf 100644 --- a/FunctionalModule/WindowRect/widget.cpp +++ b/FunctionalModule/WindowRect/widget.cpp @@ -3,6 +3,7 @@ #include #if defined(Q_OS_WIN) +#include #include #include #elif defined(Q_OS_LINUX) @@ -11,21 +12,64 @@ #elif defined(Q_OS_MAC) #endif +#if defined(Q_OS_WIN) +static HHOOK g_hook = nullptr; +/** + * @brief 处理鼠标事件的回调函数 + * @param nCode + * @param wParam + * @param lParam + * @return + */ +LRESULT CALLBACK CallBackProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + switch (wParam) + { + case WM_LBUTTONDOWN: // 鼠标左键按下 + { + POINT pos; + bool ret = GetCursorPos(&pos); + if(ret) + { + qDebug() << pos.x <<" " << pos.y; + } + qDebug() << "鼠标左键按下"; + break; + } + default: + break; + } + return CallNextHookEx(nullptr, nCode, wParam, lParam); // 注意这一行一定不能少,否则会出大问题 +} +#endif + + Widget::Widget(QWidget *parent) : QWidget(parent) { this->setWindowTitle(QString("Qt-框选鼠标当前位置窗口范围 - V%1").arg(APP_VERSION)); -// this->setAttribute(Qt::WA_TransparentForMouseEvents, true); // linux下鼠标穿透要放在后面两行代码的全前面,否则无效(但是鼠标穿透了会导致一些奇怪的问题,如找到一个不存在的窗口坐标,所以这里不使用) - this->setWindowFlags(Qt::FramelessWindowHint); // 去掉边框、标题栏 - this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明 - #if defined(Q_OS_WIN) - this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint); // 设置顶级窗口,防止遮挡(windows透明窗口默认鼠标穿透) -#elif defined(Q_OS_LINUX) - this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnBottomHint); // 由于linux下鼠标穿透有问题,不鼠标穿透当前窗口挡住,导致无法获取到正确窗口范围(会获取到当前窗口的范围),所以需要将当前窗口放到底部 -#elif defined(Q_OS_MAC) + // linux下鼠标穿透要放在后面两行代码的全前面,否则无效(但是鼠标穿透了会导致一些奇怪的问题,如窗口显示不全,所以这里不使用) + // windows下如果不设置鼠标穿透则只能捕获到当前窗口 + this->setAttribute(Qt::WA_TransparentForMouseEvents, true); #endif + this->setWindowFlags(Qt::FramelessWindowHint); // 去掉边框、标题栏 + this->setAttribute(Qt::WA_TranslucentBackground); // 背景透明 + this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint); // 设置顶级窗口,防止遮挡 +#if defined(Q_OS_WIN) + // 由于windows不透明的窗体如果不设置设置鼠标穿透WindowFromPoint只能捕捉到当前窗体,而设置鼠标穿透后想要获取鼠标事件只能通过鼠标钩子 + g_hook = SetWindowsHookExW(WH_MOUSE_LL, CallBackProc, GetModuleHandleW(nullptr), 0); // 挂载全局鼠标钩子 + if (g_hook) + { + qDebug() << "鼠标钩子挂接成功,线程ID:" << GetCurrentThreadId(); + + } + else + { + qDebug() << "鼠标钩子挂接失败:" << GetLastError(); + } +#endif // 在当前窗口上增加一层QWidget,否则不会显示边框 QGridLayout* gridLayout = new QGridLayout(this); @@ -33,15 +77,26 @@ Widget::Widget(QWidget *parent) gridLayout->setContentsMargins(0, 0, 0, 0); gridLayout->addWidget(new QWidget(), 0, 0, 1, 1); - this->setStyleSheet("border: 3px dashed rgba(58, 196, 255, 200);"); // 设置窗口边框样式 dashed虚线,solid 实线 + this->setStyleSheet(" background-color: rgba(58, 196, 255, 40); border: 2px solid rgba(58, 196, 255, 200);"); // 设置窗口边框样式 dashed虚线,solid 实线 // 使用定时器定时获取当前鼠标位置的窗口位置信息 connect(&m_timer, &QTimer::timeout, this, &Widget::on_timeout); m_timer.start(200); + } Widget::~Widget() { +#if defined(Q_OS_WIN) + if(g_hook) + { + bool ret = UnhookWindowsHookEx(g_hook); + if(ret) + { + qDebug() << "卸载鼠标钩子。"; + } + } +#endif } void Widget::on_timeout() @@ -82,6 +137,7 @@ void Widget::on_timeout() QRect recte; // 保存鼠标当前所在窗口的范围 for(unsigned int i = 0; i < nchildren; ++i) { + if(children[i] == this->winId()) continue; // 由于当前窗口一直在最顶层,所以这里要过滤掉当前窗口,否则一直获取到的就是当前窗口大小 XWindowAttributes attrs; XGetWindowAttributes(display, children[i], &attrs); // 获取窗口参数 if (attrs.map_state == IsViewable) // 只处理可见的窗口, 三个状态:IsUnmapped, IsUnviewable, IsViewable