C# 使用 DXVA2 取得/設定 外接螢幕亮度

DXVA2 直接控制實體螢幕硬體,可操作DDC/CI對外接式螢幕進行溝通

通常對筆電螢幕無效,筆電螢幕必須使用WMI

EXE執行檔下載

需要用到的 API

user32.dll

  • EnumDisplayMonitors
    1. 取得所有螢幕的 hMonitor 值
  • GetMonitorInfo
    1. 取得螢幕基本資訊(位置、主螢幕)

dxva2.dll

  • GetNumberOfPhysicalMonitorsFromHMONITOR
    1. 根據 hMonitor值來取得顯示器的數量
  • GetPhysicalMonitorsFromHMONITOR
    1. 將 HMONITOR 轉成「實體螢幕」
  • DestroyPhysicalMonitors
    1. 和GetPhysicalMonitorsFromHMONITOR配套,在程式最後又來銷毀handle
  • GetMonitorBrightness
    1. 用來取得螢幕亮度/最大值/最小值,對筆電螢幕無效

正確流程

DXVA2 不是用來「列舉螢幕」的,它是用來控制實體螢幕(亮度、對比)。

1. 所以要先用 User32 列舉所有 HMONITOR

2. 對每個 HMONITOR 用 Dxva2 取得「實體螢幕(Physical Monitor)」

EnumDisplayMonitors
    ↓
HMONITOR
    ↓
GetNumberOfPhysicalMonitorsFromHMONITOR
    ↓
GetPhysicalMonitorsFromHMONITOR
    ↓
PHYSICAL_MONITOR[]

這個範例在取得螢幕MonitorData,也把名稱加到Combobox1上,之後的應用可以隨著Combobox1的選擇

反饋comboBox1.SelectedIndex去選取monitors[i].hMonitor

private void button1_Click(object sender, EventArgs e)
{
    monitors.Clear();
    comboBox1.Items.Clear();

    EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
    // 示範:列出 List 裡的資料            
    for (int i = 0; i < monitors.Count; i++)
    {
        var m = monitors[i];
        Console.WriteLine($"[{i}] Handle   : 0x{m.hMonitor.ToInt64():X}");
        Console.WriteLine($"    Device   : {m.DeviceName}");
        Console.WriteLine($"    Primary  : {m.IsPrimary}");
        Console.WriteLine($"    Bounds   : {m.Monitor.Left},{m.Monitor.Top},{m.Monitor.Right},{m.Monitor.Bottom}");
        Console.WriteLine();
        comboBox1.Items.Add(monitors[i].DeviceName);
    }
    comboBox1.SelectedIndex = 0;
}

// 儲存所有螢幕資料
static List monitors = new List();
struct MonitorData
{
    public IntPtr hMonitor;
    public RECT Monitor;
    public RECT WorkArea;
    public bool IsPrimary;
    public string DeviceName;
}

// ====== Win32 Struct ======

[StructLayout(LayoutKind.Sequential)]
struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct MONITORINFOEX
{
    public int cbSize;
    public RECT rcMonitor;
    public RECT rcWork;
    public uint dwFlags;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string szDevice;
}

// ====== Win32 Delegate ======

delegate bool MonitorEnumDelegate(
    IntPtr hMonitor,
    IntPtr hdcMonitor,
    ref RECT lprcMonitor,
    IntPtr dwData
);

// ====== Win32 API ======

[DllImport("user32.dll")]
static extern bool EnumDisplayMonitors(
    IntPtr hdc,
    IntPtr lprcClip,
    MonitorEnumDelegate lpfnEnum,
    IntPtr dwData
);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool GetMonitorInfo(
    IntPtr hMonitor,
    ref MONITORINFOEX lpmi
);

// ====== Callback ======

static bool MonitorEnumProc(
    IntPtr hMonitor,
    IntPtr hdcMonitor,
    ref RECT lprcMonitor,
    IntPtr dwData)
{
    MONITORINFOEX info = new MONITORINFOEX();
    info.cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));

    if (GetMonitorInfo(hMonitor, ref info))
    {
        /*
        Console.WriteLine("====== Monitor ======");
        Console.WriteLine($"hMonitor   : 0x{hMonitor.ToInt64():X}");
        Console.WriteLine($"Device   : {info.szDevice}");
        Console.WriteLine($"Primary  : {(info.dwFlags == 1)}");
        Console.WriteLine($"Bounds   : {info.rcMonitor.Left}, {info.rcMonitor.Top}, {info.rcMonitor.Right}, {info.rcMonitor.Bottom}");
        Console.WriteLine($"WorkArea : {info.rcWork.Left}, {info.rcWork.Top}, {info.rcWork.Right}, {info.rcWork.Bottom}");
        Console.WriteLine();
        */
        MonitorData data = new MonitorData
        {
            hMonitor = hMonitor,
            Monitor = info.rcMonitor,
            WorkArea = info.rcWork,
            IsPrimary = (info.dwFlags & 1) != 0,
            DeviceName = info.szDevice
        };
        monitors.Add(data);
    }

    return true; // 繼續列舉
}


上個例子把所有螢幕的 hMonitor 儲存在monitors[i].hMonitor裡,接下來就用它來調整亮度

重點先講清楚(很重要)

  • HMONITOR 不能直接調亮度
  • 必須透過 Dxva2.dll 轉成 Physical Monitor
  • 一個 hMonitor 可能對應多個實體螢幕
  • 取得亮度API GetMonitorBrightness
  • 設定亮度API SetMonitorBrightness

使用流程

DXVA2 不是用來「列舉螢幕」的,它是用來控制實體螢幕(亮度、對比)。

1. 所以要先用 User32 列舉所有 HMONITOR

2. 對每個 HMONITOR 用 Dxva2 取得「實體螢幕(Physical Monitor)」

hMonitor
    ↓
GetNumberOfPhysicalMonitorsFromHMONITOR
    ↓
GetPhysicalMonitorsFromHMONITOR
    ↓
GetMonitorBrightness / SetMonitorBrightness

加上Dxva2 P/Invoke

// ====== DVXA2 ======

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct PHYSICAL_MONITOR
{
    public IntPtr hPhysicalMonitor;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string szPhysicalMonitorDescription;
}

[DllImport("dxva2.dll", SetLastError = true)]
static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(
    IntPtr hMonitor,
    out uint numberOfPhysicalMonitors
);

[DllImport("dxva2.dll", SetLastError = true)]
static extern bool GetPhysicalMonitorsFromHMONITOR(
    IntPtr hMonitor,
    uint physicalMonitorArraySize,
    [Out] PHYSICAL_MONITOR[] physicalMonitorArray
);

[DllImport("dxva2.dll", SetLastError = true)]
static extern bool DestroyPhysicalMonitors(
    uint physicalMonitorArraySize,
    PHYSICAL_MONITOR[] physicalMonitorArray
);

[DllImport("dxva2.dll", SetLastError = true)]
static extern bool GetMonitorBrightness(
    IntPtr hMonitor,
    out uint minimumBrightness,
    out uint currentBrightness,
    out uint maximumBrightness
);

[DllImport("dxva2.dll", SetLastError = true)]
static extern bool SetMonitorBrightness(
    IntPtr hMonitor,
    uint newBrightness
);

取得亮度

// ====== Get Brightness ======

static uint GetBrightness(IntPtr hMonitor)
{
    uint value = 0;
    if (!GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, out uint count) || count == 0)
        return value;

    PHYSICAL_MONITOR[] pms = new PHYSICAL_MONITOR[count];            

    if (GetPhysicalMonitorsFromHMONITOR(hMonitor, count, pms))
    {
        for (int i = 0; i < count; i++)
        {
            if (GetMonitorBrightness(
                pms[i].hPhysicalMonitor,
                out uint min,
                out uint cur,
                out uint max))
            {
                Console.WriteLine($"Brightness: {cur} (Min:{min}, Max:{max})");
                value = cur;
            }
            else
            {
                Console.WriteLine($"Not Support");
            }
        }

        DestroyPhysicalMonitors(count, pms);
    }
    return value;
}

設定亮度

// ====== Set Brightness ======

static void SetBrightness(IntPtr hMonitor, int percent)
{
    if (!GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, out uint count) || count == 0)
        return;

    PHYSICAL_MONITOR[] pms = new PHYSICAL_MONITOR[count];

    if (GetPhysicalMonitorsFromHMONITOR(hMonitor, count, pms))
    {
        for (int i = 0; i < count; i++)
        {
            if (GetMonitorBrightness(
                pms[i].hPhysicalMonitor,
                out uint min,
                out uint cur,
                out uint max))
            {
                uint newValue = (uint)(min + (max - min) * percent / 100);
                SetMonitorBrightness(pms[i].hPhysicalMonitor, newValue);
            }
            else
            {
                Console.WriteLine($"Not Support");
            }
        }

        DestroyPhysicalMonitors(count, pms);
    }
}

應用

//取得亮度
private void button3_Click(object sender, EventArgs e)
{
    uint brightness = 0;
    int index = comboBox1.SelectedIndex;
    IntPtr hMonitor = monitors[index].hMonitor;

    brightness = GetBrightness(hMonitor);
    textBox1.Text = brightness.ToString();
}

//設定亮度
private void button4_Click(object sender, EventArgs e)
{
    int brightness = 0;
    try
    {
        brightness = Convert.ToInt32(textBox2.Text);
    }
    catch (Exception ex)
    {
        textBox2.Text = 0.ToString();
    }

    brightness = brightness % 101;

    int index = comboBox1.SelectedIndex;
    IntPtr hMonitor = monitors[index].hMonitor;

    SetBrightness(hMonitor, brightness);
}

留言