提交 906b6242 编写于 作者: mahuifa's avatar mahuifa

Merge branch 'Dev'

......@@ -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
......@@ -10,8 +10,9 @@
#---------------------------------------------------------------------------------------
TEMPLATE = subdirs
SUBDIRS += QMWidget \ # qt自定义窗口
WindowRect
SUBDIRS += QMWidget # qt自定义窗口
SUBDIRS += WindowRect # 使用透明窗口框选鼠标所在窗口范围
SUBDIRS += SnippingTool # Qt实现截图工具
SUBDIRS += DeviceManagement # 串口、鼠标、键盘热插拔检测模块
SUBDIRS += QLog # 自定义日志系统
SUBDIRS += QMPlayer # 视频播放器界面
......
#---------------------------------------------------------------------------------------
# @功能: 全局鼠标、键盘事件示例
# 1windows下使用鼠标钩子实现全局鼠标监听功能
# 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需要链接库
}
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#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<QMouseEvent*>(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<QWheelEvent*>(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<MSLLHOOKSTRUCT*>(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
}
#ifndef WIDGET_H
#define WIDGET_H
#include <QMouseEvent>
#include <QWidget>
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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>581</width>
<height>505</height>
</rect>
</property>
<property name="font">
<font>
<family>宋体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="2">
<widget class="QPushButton" name="but_mouser">
<property name="text">
<string>卸载鼠标钩子</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QTextEdit" name="textEdit"/>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="but_mouseI">
<property name="text">
<string>安装鼠标钩子</string>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
#---------------------------------------------------------------------------------------
# @功能: Qt实现截图工具
# 1、实现Windowslinux系统下截图功能
# 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
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QComboBox>
#include <QDesktopWidget>
#include <QPushButton>
#include <QScreen>
#include <QThread>
#include <QToolBar>
#include <QDebug>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
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);
}
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <qtoolbutton.h>
#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
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>549</width>
<height>287</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<property name="styleSheet">
<string notr="true">/********************QToolBar样式**********************/
QToolButton {
width: 100px;
height: 30px;
}
</string>
</property>
<widget class="PlayImage" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>549</width>
<height>23</height>
</rect>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlayImage</class>
<extends>QWidget</extends>
<header>playimage.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
#include "playimage.h"
#include <QPainter>
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);
}
#ifndef PLAYIMAGE_H
#define PLAYIMAGE_H
#include <QWidget>
#include <qmutex.h>
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
<RCC>
<qresource prefix="/">
<file>img/保存.ico</file>
<file>img/剪切.ico</file>
<file>img/取消.ico</file>
<file>img/选区.ico</file>
</qresource>
</RCC>
#include "screenrect.h"
#include <qapplication.h>
#include <qpainter.h>
#include <QDesktopWidget>
#include <qscreen.h>
#include <QDebug>
#include <QMouseEvent>
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); // 绘制选择的矩形框
}
/******************************************************************************
* @文件名 screenrect.h
* @功能 矩形截图选框窗口
*
* @开发者 mhf
* @邮箱 1603291350@qq.com
* @时间 2022/11/18
* @备注
*****************************************************************************/
#ifndef SCREENRECT_H
#define SCREENRECT_H
#include <QWidget>
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
#include "windowrect.h"
#include <QDebug>
#include <QGridLayout>
#include <QEvent>
#include <QMouseEvent>
#if defined(Q_OS_WIN)
#include <Windows.h>
#include <windef.h>
#elif defined(Q_OS_LINUX)
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#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() <<recte<< this->windowState();
// 注意释放资源
XFree(children);
XCloseDisplay(display);
#elif defined(Q_OS_MAC)
#endif
}
/******************************************************************************
* @文件名 windowrect.h
* @功能 选中鼠标当前位置窗口的类
*
* @开发者 mhf
* @邮箱 1603291350@qq.com
* @时间 2022/11/19
* @备注
*****************************************************************************/
#ifndef WINDOWRECT_H
#define WINDOWRECT_H
#include <QTimer>
#include <QWidget>
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
#---------------------------------------------------------------------------------------
# @功能: 框选鼠标当前位置窗口范围(类似窗口截图)
# 1.使用WindowsAPI实现windows下功能
# 2.使用x11 API实现linuxubuntu)下功能。
# 2.使用x11 API实现linuxubuntu)下功能;
# 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位编译器
......
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
......
......@@ -3,6 +3,7 @@
#include <qgridlayout.h>
#if defined(Q_OS_WIN)
#include <QPushButton>
#include <Windows.h>
#include <windef.h>
#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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册