wine屏幕模式获取/设置适配

我们公司的软件采用WPF开发,最近需要迁移到国产操作系统上运行,通过方案对比,最后采用wine直接进行适配。
在我们的软件中有一个功能,就是获取当前屏幕的模式,并可以设置屏幕的模式
模式获取和设置
在适配后发现linux中运行时功能部分有效,部分无效,具体如下:

  • 获取当前屏幕数量:成功
  • 获取屏幕模式:失败
  • 设置屏幕模式:失败

一、当前WPF中相应功能的代码

1. 获取屏幕数量(wine适配有效)

/// <summary>
/// 获取是否多屏
/// </summary>
/// <returns></returns>
public static bool GetIsMultiScreen()
{
    var status = GetDisplayConfigBufferSizes(
        QueryDisplayFlags.DatabaseCurrent,
        out var numPathArrayElements,
        out _);
    return numPathArrayElements > 1;
}
[DllImport("User32.dll")]
public static extern StatusCode GetDisplayConfigBufferSizes(
    QueryDisplayFlags flags,
    out int numPathArrayElements,
    out int numModeInfoArrayElements);
[Flags]
public enum QueryDisplayFlags : uint
{
    Zero = 0x0,
    AllPaths = 0x00000001,
    OnlyActivePaths = 0x00000002,
    DatabaseCurrent = 0x00000004
}

2. 获取屏幕模式(wine适配失败)

 /// <summary>
 /// 获取是否是扩展模式
 /// </summary>
 /// <returns></returns>
 public static bool GetIsExtentModel()
 {
     var id = GetCurrentMode();
     return id == DisplayConfigTopologyId.Extend;
 }
private static DisplayConfigTopologyId GetCurrentMode(
    QueryDisplayFlags pathType = QueryDisplayFlags.DatabaseCurrent)
{
    DisplayConfigTopologyId topologyId = DisplayConfigTopologyId.Zero;
    int numPathArrayElements;
    int numModeInfoArrayElements;

    var status = GetDisplayConfigBufferSizes(
        pathType,
        out numPathArrayElements,
        out numModeInfoArrayElements);
    if (status != StatusCode.Success)
    {
        //POSSIBLY HANDLE SOME OF THE CASES.
        var reason = string.Format("GetDisplayConfigBufferSizesFailed() failed. Status: {0}", status);
        throw new Exception(reason);
    }

    var pathInfoArray = new DisplayConfigPathInfo[numPathArrayElements];
    var modeInfoArray = new DisplayConfigModeInfo[numModeInfoArrayElements];

    var queryDisplayStatus = pathType == QueryDisplayFlags.DatabaseCurrent
        ? QueryDisplayConfig(
            pathType,
            ref numPathArrayElements, pathInfoArray,
            ref numModeInfoArrayElements, modeInfoArray, out topologyId)
        : QueryDisplayConfig(
            pathType,
            ref numPathArrayElements, pathInfoArray,
            ref numModeInfoArrayElements, modeInfoArray);
    if (queryDisplayStatus != StatusCode.Success)
    {
        // POSSIBLY HANDLE SOME OF THE CASES.
        var reason = string.Format("QueryDisplayConfig() failed. Status: {0}", queryDisplayStatus);
        throw new Exception(reason);
    }

    return topologyId;
}
[DllImport("User32.dll")]
private static extern StatusCode QueryDisplayConfig(
    QueryDisplayFlags flags,
    ref int numPathArrayElements,
    [Out] DisplayConfigPathInfo[] pathInfoArray,
    ref int modeInfoArrayElements,
    [Out] DisplayConfigModeInfo[] modeInfoArray,
    IntPtr topologyId = default
);
[Flags]
private enum DisplayConfigTopologyId : uint
{
    Zero = 0x0,
    Internal = 0x00000001,
    Clone = 0x00000002,
    Extend = 0x00000004,
    External = 0x00000008,
}
public enum StatusCode : uint
{
    Success = 0,
    InvalidParameter = 87,
    NotSupported = 50,
    AccessDenied = 5,
    GenFailure = 31,
    BadConfiguration = 1610,
    InSufficientBuffer = 122,
}

[StructLayout(LayoutKind.Sequential)]
private struct DisplayConfigPathInfo
{
    public DisplayConfigPathSourceInfo sourceInfo;
    public DisplayConfigPathTargetInfo targetInfo;
    public DisplayConfigFlags flags;
}

[StructLayout(LayoutKind.Sequential)]
private struct DisplayConfigPathSourceInfo
{
    public LUID adapterId;
    public uint id;
    public uint modeInfoIdx;

    public DisplayConfigSourceStatus statusFlags;
}

3. 设置屏幕模式(wine适配失败)

        /// <summary>
        /// 设置显示器复制模式
        /// </summary>
        public static void SetDisplayCloneModel()
        {
            SetDisplayModel(DisplayConfigTopologyId.Clone);
        }

        /// <summary>
        /// 设置显示器扩展模式
        /// </summary>
        public static void SetDisplayExtentModel()
        {
            SetDisplayModel(DisplayConfigTopologyId.Extend);
        }
        /// <summary>
        /// 设置显示模式
        /// </summary>
        /// <param name="id"></param>
        private static void SetDisplayModel(DisplayConfigTopologyId id)
        {
            //最终决定使用老代码 bb 2021-03-15 12:00:46
            SetDisplayModelOld(id);
            //调用新的操作设置显示模式 bb 2021-03-04 10:10:09
            //SetDisplayModelNew(id);
        }
        /// <summary>
        /// 设置显示器模式
        /// </summary>
        /// <param name="id"></param>
        private static void SetDisplayModelOld(DisplayConfigTopologyId id)
        {
            SetDisplayConfig(0, IntPtr.Zero, 0, IntPtr.Zero, SDC_APPLY | (uint)id);
        }
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern long SetDisplayConfig(uint numPathArrayElements, IntPtr pathArray,uint numModeArrayElements,IntPtr modeArray, uint flags);
        private const uint SDC_APPLY = 0x00000080;

二、问题排查和分析

1. 获取屏幕数量

没有问题,不需要排查

2. 获取屏幕模式

发现在调用GetIsExtentModel方法时,返回永远是false(复制模式),通过跟踪定位,发现问题是出在QueryDisplayConfig这个方法上,在调用这个方法返回状态位success,但是topologyId参数一直返回DisplayConfigTopologyId.Internal,查看win的源码如下:
wine github地址
在这里插入图片描述
仔细分析了下源代码,发现代码只是做了参数判断,实际上没有真正执行获取屏幕模式的代码:
在这里插入图片描述
结论:采用QueryDisplayConfig方法获取屏幕模式在wine中没有适配,暂不能使用,需要考虑替代方案

3. 设置屏幕模式

设置屏幕模式采用的SetDisplayConfig方法,通过监控发现,方法返回成功,但是设置没有生效,通过wine源码查看发现,源码只是对方法进行了打桩,并没有真实适配:
在这里插入图片描述
结论:设置屏幕模式的代码在wine中没有适配,暂不能使用,需要考虑替代方案

二、解决方案

1. 获取屏幕数量

没有问题,不需要解决

2. 获取屏幕模式

通过查找,发现获取屏幕模式还可以通过变相的解决方案:
采用GetSystemMetrics获取主屏和扩展屏的坐标,判断是否是复制模式:

        /// <summary>
        /// 获取是否是扩展模式
        /// </summary>
        /// <returns></returns>
        private bool GetIsExtentModel()
        {
            int cxVirtual =GetSystemMetrics(SM_CXVIRTUALSCREEN);
            int cyVirtual = GetSystemMetrics(SM_CYVIRTUALSCREEN);
            int cxScreen = GetSystemMetrics(SM_CXSCREEN);
            int cyScreen = GetSystemMetrics(SM_CYSCREEN);
            // 如果虚拟屏幕的尺寸等于单个屏幕的尺寸,则可能是复制模式
            if (cxVirtual == cxScreen && cyVirtual == cyScreen)
            {
                // 可能是复制模式
                return true;
            }
            // 如果虚拟屏幕的尺寸大于单个屏幕的尺寸,则可能是扩展模式
            else
            {
                // 可能是扩展模式
                return false;
            }
        }
        [DllImport("user32.dll")]
        public static extern int GetSystemMetrics(int nIndex);
        private const int SM_CYSCREEN = 1;
        private const int SM_CXSCREEN = 0;
        private const int SM_CYVIRTUALSCREEN = 79;
        private const int SM_CXVIRTUALSCREEN = 78;

GetSystemMetrics 方法详解

3. 设置屏幕模式

网上有说采用ChangeDisplaySettingsEx来间接设置屏幕模式的办法,但是我没有找到一个有效的方案,我这里指定另一种解决方案:
采用linux原生命令来修改屏幕模式。
使用Xrandr命令来设置复制模式和扩展模式

//设置扩展模式命令
xrandr --output HDMI-1 --auto --right-of LVDS-1
//设置复制模式命令
xrandr --output VGA-1 --same-as HDMI-1

如果你需要在C++程序中调用 xrandr 命令,可以使用 std::system 函数:

#include <cstdlib>

void setCloneMode() 
{
    //设置扩展模式命令
	system("xrandr --output HDMI-1 --auto --right-of LVDS-1");
	//设置复制模式命令
	system("xrandr --output VGA-1 --same-as HDMI-1");
}

xrandr详细说明如下:xrandr 命令,Linux xrandr 命令详解
注:虽然xrandr能够完成模式切换,但是xrandr切换为扩展模式时需要手动指定2个屏幕的左右方向,许多场景是不满足的 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值