[Share Experiences] 尝试修好一个bug (二)定位、分析和修复问题
Tofloor
poster avatar
冒得先生
deepin
2022-03-14 07:02
Author

出发点

发帖子的时候,发现deepin自带的截屏工具在自己的电脑上工作不正常。确认了一下,自己是最新的版本,确认是一个目前存在的bug了。想着自己怎么的也是一个程序员,改bug最熟练了,而且这应该是一个开源的工具,那么就自己改一改。虽然没有任何的上下文。

俗话说满桶不会响,半桶响叮当。我这个半桶水希望能够记录下来,自己试图解决这样一个bug过程中,遇到的问题。或许其它开发者看到,不说改进,算是帮人蹚雷吧。

bug描述系统配置,见之前的帖子 尝试修好一个bug(一)

寻找问题的出处

一点上下文

在本地编译构建,复现了问题之后,紧接着就是定位bug的位置所在了。为了读者理解通顺,我解释一下我电脑显示器的排列。

| DisplayPort-2 | DisplayPort-0 | DisplayPort-4 |
| DisplayPort-3 | DisplayPort-1 | DisplayPort-5 |

一共6台4k显示器,分辨率都是3840x2160,dpi设置是150(放大1.5倍)。换算成等效的分辨率,就是2560x1440(2560 = 3840 / 1.5)。把显示器的位置和大小复合表示,以DisplayPort-5为例,3840x2160+7680+2160。前两个数字是分辨率,有两个数值是相对与拼接复合画面的左上角的偏移量。

一点点分析

根据问题的表现来分析和猜测:

  • 在一个屏幕和两个屏幕上,没有复现问题,那么这个问题是与更多屏幕相关。
  • 在选择DisplayPort3上的内容时,高亮的框显示在DisplayPort2 上,可能是屏幕的映射关系出问题。
  • 但是在选择DisplayPort1上的内容时,高亮框横跨到了Display5上,估计不是映射,而是数值计算出错。
  • 每次选择同一个窗体时,错误显示的高亮框,位置是一定的。所以问题应该和鼠标的位置无关,而是高亮框的位置计算出了问题。
  • 我有调整显示器的dpi到150,或许是dpi和分辨率的对应关系出错。但是看每个显示器错误显示的位置没有和dpi数值的强相关性。
  • 位置错误的高亮窗口,宽和高是对的,问题应该是在偏移量上。

想得太多,还是一边调试代码,一边看好了。

进入源代码

先放一下项目的代码仓库地址。对于qt的项目,首先就看MainWindow代码的逻辑。好家伙,5千行的文件,不愧是c++。硬着头从init开始看把。

先看了一下initMainWindow这个函数。里面有初始化每个屏幕大小和偏移量的逻辑。m_screenInfo 这个变量存了屏幕信息的list。我检查了生成的每一个屏幕信息,对于我的电脑而言,就是6个显示器的参数,确实都是正确的。dpi获取的数值也是

struct ScreenInfo {
    int x;
    int y;
    int width;
    int height;
    QString name;
    ~ScreenInfo() {}
};

既然不是这里,那就继续找。然后看initAttributes这个函数。发现我的debugger会因为段错误异常退出。从更上层screenshot.cpp里,step into。找到了段错误的位置。

 setWindowTitle(tr("Screen Capture"));
 m_keyButtonList.clear();
 m_isZhaoxin = Utils::checkCpuIsZhaoxin(); # 执行到这一行段错误了

看起来是为了兆芯准备的代码,注释掉,不管。继续往后找。然后倍initShortcut函数强行中断,应该和bug无关,注释掉,继续往后走。

接下来,我被一个名字叫做widthAfterFirst的变量吸引了注意力。这个变量,算出了一个19200的天文数字,看起来不太对。不过和我遇到的bug表现形式来推断,应该不是(窗口的偏移量没有这么大)。虽然看着像有问题,但是依然选择忽略。

然后是一个move的函数。这种移动窗体的函数,我非常感兴趣,因为或许是移动计算逻辑出错。 但是我找不到具体的定义位置。那应该是基类里的方法。看一下DWidget。找到了DWdiget.h

// DWidget.h

#include "dwidgetstype.h"
#include 

啊?!,还好还好,这个不是完全重命名QWidget。我看看第一个头文件里定义了啥。

...

class QUndoView;
class QWhatsThis;
class QWizard;
class QWizardPage;
class QSizeGrip;

QT_END_NAMESPACE

DWIDGET_BEGIN_NAMESPACE

typedef QScrollBar DScrollBar;
typedef QPushButton DPushButton;
typedef QRadioButton  DRadioButton;
typedef QDialogButtonBox DDialogButtonBox;
typedef QListWidget DListWidget;

...

呀哈哈…… 看来,这里没有bug相关的信息,我直接去看QWidget文档好了。看了一下,move只是移动框口位置,没有计算逻辑。放过这个move函数了。

柳暗花明又一村

初始化没有看出来什么问题,那只好从信号系统去找问题。至少,每一次重新选择窗体的时候,都会有高亮窗口的变化,那么一定是有事件响应函数的。所以一点一点找,resizeLeft -> eventFilter -> recordX , 诶, 这个recordX变量,看起来就是每次调整高亮窗口的偏移量。打印了一下,发现算出来的值确实不对!!找到问题代码了!!

分析问题的原因

先把计算高亮框体位置的代码逻辑放在下面

const QPoint mousePoint = QCursor::pos();
for (auto it = windowRects.rbegin(); it != windowRects.rend(); ++it) {
    if (QRect(it->x(), it->y(), it->width(), it->height()).contains(mousePoint)) {
        if (!qFuzzyCompare(1.0, m_pixelRatio) && m_screenCount > 1) {
            int x = it->x();
            int y = it->y();
            int beforeWidth = 0;
            for (int index = 0; index < m_screenCount; ++index) {
                if (x >= m_screenInfo[index].x && x < (m_screenInfo[index].x + m_screenInfo[index].width)) {
                    recordX = static_cast((x - m_screenInfo[index].x) + beforeWidth / m_pixelRatio);
                    break;
                }
                beforeWidth += m_screenInfo[index].width;
            }
            int beforeHeight = 0;
            for (int index = 0; index < m_screenCount; ++index) {
                if (y >= m_screenInfo[index].y && y < (m_screenInfo[index].y + m_screenInfo[index].height)) {
                    recordY = static_cast((y - m_screenInfo[index].y) + beforeHeight / m_pixelRatio);
                    break;
                }
                beforeHeight += m_screenInfo[index].height;
            }

        } else {
            recordX = it->x() - static_cast(screenRect.x() * m_pixelRatio);
            recordY = it->y() - static_cast(screenRect.y() * m_pixelRatio);
        }
        recordWidth = it->width();
        recordHeight = it->height();
        needRepaint = true;
        break;
    }
}

这个逻辑中,对于多个显示器,或者缩放率不是1的情况,进行了计算。这里有一个beforeWidth和beforeHeight的计算。是统计,在当前这个显示器的左侧,和上方,的偏移量是多少。

我打印了一下,计算出来的beforeWidth, 发现,对于DisplayPort1(中间下方的显示器)的内容,有7680。仔细看这段代码的逻辑,发现了问题。因为我显示器的排列是3×2,所以,对于DisplayPort1,左侧确实有两块显示器,但是他们的宽度应该只需要计算一次。按照上面的代码,会导致一些显示器的宽高被重复计算了。

解决问题的代码

找到了问题,那就很简单。我不需要知道一台显示器的左侧和上方有几台显示器。确认鼠标所在的显示器,在所在显示器的信息里,就有偏移量,直接用就好了。

下面是更改后的代码

for (auto it = windowRects.rbegin(); it != windowRects.rend(); ++it) {
         if (!qFuzzyCompare(1.0, m_pixelRatio) && m_screenCount > 1) {
             int x = it->x();
             int y = it->y();
-            int beforeWidth = 0;
-            for (int index = 0; index < m_screenCount; ++index) {
-                if (x >= m_screenInfo[index].x && x < (m_screenInfo[index].x + m_screenInfo[index].width)) {
-                    recordX = static_cast((x - m_screenInfo[index].x) + beforeWidth / m_pixelRatio);
-                    break;
-                }
-                beforeWidth += m_screenInfo[index].width;
-            }
-            int beforeHeight = 0;
-            for (int index = 0; index < m_screenCount; ++index) {
-                if (y >= m_screenInfo[index].y && y < (m_screenInfo[index].y + m_screenInfo[index].height)) {
-                    recordY = static_cast((y - m_screenInfo[index].y) + beforeHeight / m_pixelRatio);
-                    break;
-                }
-                beforeHeight += m_screenInfo[index].height;
-            }

+            ScreenInfo info = m_screenInfo[0];
+            for ( int index = 0; index < m_screenCount; ++index) {
+                auto si = m_screenInfo[index];
+                if ( x >= si.x && x < si.x + si.width && y >= si.y && y < si.y + si.height) {
+                    info = si;
+                    break;
+                }
+            }
+            recordX = static_cast((x - info.x) + info.x / m_pixelRatio);
+            recordY = static_cast((y - info.y) + info.y / m_pixelRatio);
         } else {
             recordX = it->x() - static_cast(screenRect.x() * m_pixelRatio);
             recordY = it->y() - static_cast(screenRect.y() * m_pixelRatio);
         }
         recordWidth = it->width();
         recordHeight = it->height();
         needRepaint = true;
         break;
     }
}

编译,运行,程序可以正确的高亮鼠标所在的窗口了。截图,内容也是高亮区域的内容。问题解决了。

感想和后续

在对qt有基础了解的情况下,寻找问题所在还是比较符合直觉的。一点一点调试,就能够找到问题所在。在周末把这个bug找到并且解决,内心是非常激动的。这是我第一次试图在开源项目上定位和解决问题,比想象中的要容易很多。虽然在开始之前对于项目一无所知,但是按照解决问题的基本流程来走,每一步都还是比较顺畅的。

这个问题虽然解决了,但是,由于没有找到项目的单元测试代码,不知道是否会破坏已有功能。为了保险,还需要进一步人工测试,还有写代码测试。这些,或许需要重构代码,或者调整实现。当然,这些都离不开和项目维护者的沟通。不知道论坛里有没有这个项目的维护人员,以及能否看到这篇帖子。如果联系不上,估计测试完成,就只能直接提交PR了。

Reply Favorite View the author
All Replies
q77190858
deepin
2022-03-14 09:32
#1

牛逼牛逼,膜拜大佬

Reply View the author
jjcui8595
deepin
2022-03-14 16:04
#2

大神!大赞👍

Reply View the author
ngc-1952
deepin
2022-03-15 01:12
#3

like

Reply View the author
deepin-superuser
deepin
2022-03-15 05:23
#4

膜拜大佬,希望以后多多输出这类型高质量文章

Reply View the author
deepin-superuser
deepin
2022-03-15 05:38
#5
for (auto it = windowRects.rbegin(); it != windowRects.rend(); ++it) {
         if (!qFuzzyCompare(1.0, m_pixelRatio) && m_screenCount > 1) {
             int x = it->x();
             int y = it->y();
-            int beforeWidth = 0;
-            for (int index = 0; index < m_screenCount; ++index) {
-                if (x >= m_screenInfo[index].x && x < (m_screenInfo[index].x + m_screenInfo[index].width)) {
-                    recordX = static_cast((x - m_screenInfo[index].x) + beforeWidth / m_pixelRatio);
-                    break;
-                }
-                beforeWidth += m_screenInfo[index].width;
-            }
-            int beforeHeight = 0;
-            for (int index = 0; index < m_screenCount; ++index) {
-                if (y >= m_screenInfo[index].y && y < (m_screenInfo[index].y + m_screenInfo[index].height)) {
-                    recordY = static_cast((y - m_screenInfo[index].y) + beforeHeight / m_pixelRatio);
+            ScreenInfo info = m_screenInfo[0];
+
+            for ( int index = 0; index < m_screenCount; ++index) {
+                auto si = m_screenInfo[index];
+                if ( x >= si.x && x < si.x + si.width && y >= si.y && y < si.y + si.height) {
+                    info = si;
                     break;
                 }
-                beforeHeight += m_screenInfo[index].height;
             }
 
+            recordX = static_cast((x - info.x) + info.x / m_pixelRatio);
+            recordY = static_cast((y - info.y) + info.y / m_pixelRatio);
         } else {
             recordX = it->x() - static_cast(screenRect.x() * m_pixelRatio);
             recordY = it->y() - static_cast(screenRect.y() * m_pixelRatio);

Reply View the author