0%

【Linux技术分享】呼叫器窗口问题分析

呼叫器窗口问题分析

问题概述

呼叫器在nfs系统中启动后,在菜单栏无图标显示,但窗口正常显示,用户在最小化窗口时无法找到窗口再启动

正常现象如下图:

图1

异常现象如下图:

图2

经验证问题复现环境为:

软件环境:amd架构,nfs-5.0-G212p版本

硬件环境:需要amd架构处理器

wine版本:上游wine-8.6

问题分析

  1. 观察现象,根据曾经的mfc,x11编程经验来看,该类问题的出现有两种可能的原因:
    • 猜测1:在linux下怀疑启动窗口存在隐藏图标属性
    • 猜测2:是在创建菜单栏时,加载rc的api在wine下不支持或者存在bug
    • 猜测3:在nfs系统下,任务栏加载这个进程时图标显示失败

分析验证

猜测1:

  1. 介绍两个观察x窗口的工具,xprop与xwininfo
     - xwininfo:列出窗口的基本几何信息和状态,简单用法是在终端里面执行命令 `xwininfo` 之后, 此时系统鼠标光标被 `xwininfo` 捕获,光标变成十字星形状,移动鼠标点击要查看的窗口的任意区域,执行`xwininfo`的终端输出了该窗口的这些 X11 属性.
     - 列出窗口的全部X11属性,简单用法是在终端里面执行命令 `xprop` 之后, 此时系统鼠标光标被 `xprop` 捕获,光标变成十字星形状,移动鼠标点击要查看的窗口的任意区域,执行`xprop`的终端输出了该窗口的所有 X11 属性.
    
  2. 通过xwininfo获取窗口id,再通过xprop -id _NET_WM_STATE | grep “_NET_WM_STATE_SKIP_TASKBAR”,判断呼叫器窗口未设置隐藏菜单栏窗口属性

猜测2:

  1. 开启api monitor监控,按照问题在nfs上复现的步骤在windows上操作呼叫器,关注loadiconloadimgecreateicon,等关于hfont创建加载的函数,发现有两个创建有icon资源加载
    图3
    图4
  2. 发现存在两个窗口都有icon资源加载,通过spy++确定目标创建句柄,通过api monitor或者od观察句柄创建后的函数,发现窗口调用ole.dllOleLoadPictureExt函数加载资源并在createwindow下进行赋值。
    图5
  3. 本地实现简单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;
    // 设置窗口类的图标
    }
    }
  4. 发现图标显示正常显示,日志也与呼叫器窗口创建保持一致

猜测3:

  1. 菜单栏图标显示简介:菜单栏在对系统来说也是一个进程,每个进程在菜单栏进行显示需要进程提供窗口类型,窗口消息,窗口图标等,同样菜单栏移除显示也会遵循某种规则
  2. 通过猜测1中的xwininfoxwininfo -tree -root命令获取到系统菜单栏的进程是cods-desktop
    图6
  3. cdos是Cromemco 推出的一种类似 CP/M 的操作系统,猜测方德是桌面系统是沿用cdos系统源码,但菜单栏属于深度diy的代码,上游开源代码可能与nfs相差比较大,所以暂未考虑查看上游代码进行排查问题
  4. cdos-desktop --help发现增加参数可以输出日志,编写脚本将日志输出至终端
    1
    2
    3
    4
    #/bin/sh
    export GTK_DEBUG=all
    killall cdos-desktop
    cdos-desktop -b
  5. 运行程序捕捉日志,发现呼叫器启动后菜单栏将其进行移除
    图7
  6. 通过猜测2中的源码观察,大致可以理清呼叫器窗口创建流程,创建父窗口->创建子窗口->隐藏父窗口,编写demo进行验证,demo较简单(/呼叫器/code/win.c)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    hwndParent = 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);
  7. 在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);
    修改后效果如下:
    图8

小结

一个问题产生后,可以先看看问题现象,找到产生问题的操作规律,从操作上可以大胆推测问题出现的可能出现的几种原因,然后再通过测试用例或者其它手段一一排除。同样的功能,代码实现的方法有多种,写测试用例前,可以网上查看下实现一个功能比较常见的几种手法,这样可以缩小apimonitor监控的过滤范围。结合apimonitor调用的接口,再写测试用例就可以比较准确的复现问题。