呼叫器窗口问题分析
问题概述
呼叫器在nfs系统中启动后,在菜单栏无图标显示,但窗口正常显示,用户在最小化窗口时无法找到窗口再启动
正常现象如下图:
异常现象如下图:
经验证问题复现环境为:
软件环境:amd架构,nfs-5.0-G212p版本
硬件环境:需要amd架构处理器
wine版本:上游wine-8.6
问题分析
- 观察现象,根据曾经的mfc,x11编程经验来看,该类问题的出现有两种可能的原因:
- 猜测1:在linux下怀疑启动窗口存在隐藏图标属性
- 猜测2:是在创建菜单栏时,加载rc的api在wine下不支持或者存在bug
- 猜测3:在nfs系统下,任务栏加载这个进程时图标显示失败
分析验证
猜测1:
- 介绍两个观察x窗口的工具,xprop与xwininfo
- xwininfo:列出窗口的基本几何信息和状态,简单用法是在终端里面执行命令 `xwininfo` 之后, 此时系统鼠标光标被 `xwininfo` 捕获,光标变成十字星形状,移动鼠标点击要查看的窗口的任意区域,执行`xwininfo`的终端输出了该窗口的这些 X11 属性. - 列出窗口的全部X11属性,简单用法是在终端里面执行命令 `xprop` 之后, 此时系统鼠标光标被 `xprop` 捕获,光标变成十字星形状,移动鼠标点击要查看的窗口的任意区域,执行`xprop`的终端输出了该窗口的所有 X11 属性.
- 通过xwininfo获取窗口id,再通过xprop -id
_NET_WM_STATE | grep “_NET_WM_STATE_SKIP_TASKBAR”,判断呼叫器窗口未设置隐藏菜单栏窗口属性
猜测2:
- 开启api monitor监控,按照问题在nfs上复现的步骤在windows上操作呼叫器,关注
loadicon
,loadimge
,createicon
,等关于hfont创建加载的函数,发现有两个创建有icon
资源加载 - 发现存在两个窗口都有
icon
资源加载,通过spy++
确定目标创建句柄,通过api monitor
或者od
观察句柄创建后的函数,发现窗口调用ole.dll
的OleLoadPictureExt
函数加载资源并在createwindow
下进行赋值。 - 本地实现简单demo,加载图片资源然后创建窗口进行赋值,如下(/呼叫器/code/demo.c):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 加载图像并设置为窗口图标
HICON LoadAndSetWindowIcon()
{
CoInitialize(NULL);
// 假设pStream已经被初始化并包含了图片数据
IStream *pStream = ReadImageToLPSTREAM();
// 图片的宽度和高度(设为0表示使用默认的图片尺寸)
LONG width = 0;
LONG height = 0;
// 是否保持图片的比例
BOOL keepAspectRatio = TRUE;
// 加载图片
IPicture *pPicture = NULL;
HRESULT hr = OleLoadPictureEx(pStream, 0, FALSE, IID_IMyInterface, 0, 0, 0, (void **)&pPicture);
if (SUCCEEDED(hr))
{
// 获取图标句柄
HICON hIcon = NULL;
pPicture->get_Handle((OLE_HANDLE*)&hIcon);
return hIcon;
// 设置窗口类的图标
}
} - 发现图标显示正常显示,日志也与呼叫器窗口创建保持一致
猜测3:
- 菜单栏图标显示简介:菜单栏在对系统来说也是一个进程,每个进程在菜单栏进行显示需要进程提供窗口类型,窗口消息,窗口图标等,同样菜单栏移除显示也会遵循某种规则
- 通过猜测1中的
xwininfo
的xwininfo -tree -root
命令获取到系统菜单栏的进程是cods-desktop
cdos
是Cromemco 推出的一种类似 CP/M 的操作系统,猜测方德是桌面系统是沿用cdos系统源码,但菜单栏属于深度diy的代码,上游开源代码可能与nfs相差比较大,所以暂未考虑查看上游代码进行排查问题cdos-desktop --help
发现增加参数可以输出日志,编写脚本将日志输出至终端1
2
3
4/bin/sh
export GTK_DEBUG=all
killall cdos-desktop
cdos-desktop -b- 运行程序捕捉日志,发现呼叫器启动后菜单栏将其进行移除
- 通过猜测2中的源码观察,大致可以理清呼叫器窗口创建流程,创建父窗口->创建子窗口->隐藏父窗口,编写demo进行验证,demo较简单(/呼叫器/code/win.c)
1
2
3
4
5
6
7
8
9hwndParent = CreateWindowEx(0, parentClassName, "Parent", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 800, NULL, NULL, hInstance, NULL);
ShowWindow(hwndParent, nCmdShow);
UpdateWindow(hwndParent);
// 创建子窗口
hwndChild = CreateWindowEx(0, childClassName, "child", WS_OVERLAPPEDWINDOW, 200, 200, 200, 150, hwndParent, NULL, hInstance, NULL);
ShowWindow(hwndParent, SW_HIDE);
ShowWindow(hwndChild, nCmdShow);
UpdateWindow(hwndChild); - 在nfs系统上进行验证,发现同样菜单栏下无法显示图标
分析结论
通过上述分析与验证代码,可以初步猜测在nfs系统上使用wine创建子窗口与父窗口时隐藏父窗口,会使cdos-desktop程序将进程任务栏图标进行移除。在nfs验证后,将同样wine代码与测试代码在deepin进行验证,未发现异常问题,图标正常显示。
解决方案
- 方案1: 与系统组沟通修改cdos-desktop的bug(时间较长,暂不考虑)
- 方案2:修改父窗口属性,在父窗口被移除时的窗口属性中,不增加
_NET_WM_STATE_SKIP_TASKBAR
代码如下修改后效果如下:1
2
3
4
5
6
7
8
9
10@@ -1059,6 +1059,9 @@ void update_net_wm_states( struct x11drv_win_data *data )
if (!(new_state & (1 << i))) continue;
TRACE( "setting wm state %u for unmapped window %p/%lx\n",
i, data->hwnd, data->whole_window );
+ if(i == NET_WM_STATE_SKIP_TASKBAR) {
+ continue;
+ }
atoms[count++] = X11DRV_Atoms[net_wm_state_atoms[i] - FIRST_XATOM];
if (net_wm_state_atoms[i] == XATOM__NET_WM_STATE_MAXIMIZED_VERT)
atoms[count++] = x11drv_atom(_NET_WM_STATE_MAXIMIZED_HORZ);
小结
一个问题产生后,可以先看看问题现象,找到产生问题的操作规律,从操作上可以大胆推测问题出现的可能出现的几种原因,然后再通过测试用例或者其它手段一一排除。同样的功能,代码实现的方法有多种,写测试用例前,可以网上查看下实现一个功能比较常见的几种手法,这样可以缩小apimonitor监控的过滤范围。结合apimonitor调用的接口,再写测试用例就可以比较准确的复现问题。