2012/07/30(Mon)Win32APIで画面の明るさを変更する方法

2012/07/30 22:07 Software::Windows
とある事情で、ノートPCのディスプレイの明るさを動的に変更したくなったのだけど、意外と情報がまとまっていなかったのでメモ。
なお、API的にWindows VistaまたはServer 2008以降のみをサポートしており、Windows XPでは使用できないAPIを使っているので注意。
.net Framework 2.0で確認済み。

使うAPI一覧

Vistaから一新された電源管理DLLのPowrProf.dll*1に揃っている。
PowerReadACValueIndex
AC接続時の電源設定の読み出し (MSDN)
PowerWriteACValueIndex
AC接続時の電源設定の書き込み (MSDN)
PowerReadDCValueIndex
DC接続時の電源設定の読み出し (MSDN)
PowerWriteDCValueIndex
DC接続時の電源設定の書き込み (MSDN)
PowerSetActiveScheme
現在のユーザ環境に電源設定を再適用する (MSDN)

*1 : よくPowerProf.dllとtypoしやすいので注意。

P/Invoke

C4F Developer KitPowerManagementNative.csから引用・参考にさせてもらうと…
#region Constants
/// <summary>
/// Settings in this subgroup control configuration of the video power management features.
/// </summary>
public Guid GUID_VIDEO_SUBGROUP = new Guid("7516b95f-f776-4464-8c53-06167f40cc99");

/// <summary>
/// Guid for display brightness setting.
/// </summary>
public Guid DisplayBrightnessGuid = new Guid("aded5e82-b909-4619-9949-f5d71dac0bcb");
#endregion

#region Methods
/// <summary>
/// Retrieves the AC index of the specified power setting.
/// </summary>
/// <param name="RootPowerKey">This parameter is reserved for future use and must be set to NULL.</param>
/// <param name="SchemeGuid">The identifier of the power scheme.</param>
/// <param name="SubGroupOfPowerSettingsGuid">The subgroup of power settings. This parameter can be one of the following values defined in WinNT.h. Use NO_SUBGROUP_GUID to refer to the default power scheme.</param>
/// <param name="PowerSettingGuid">The identifier of the power setting</param>
/// <param name="AcValueIndex">A pointer to a variable that receives the AC value index.</param>
/// <returns>Returns ERROR_SUCCESS (zero) if the call was successful, and a nonzero value if the call failed.</returns>
[DllImport("powrprof.dll", EntryPoint = "PowerReadACValueIndex", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint PowerReadACValueIndex(IntPtr RootPowerKey, ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, out uint AcValueIndex);


/// <summary>
/// Sets the AC value index of the specified power setting.
/// </summary>
/// <param name="RootPowerKey">This parameter is reserved for future use and must be set to NULL.</param>
/// <param name="SchemeGuid">The identifier of the power scheme.</param>
/// <param name="SubGroupOfPowerSettingsGuid">The subgroup of power settings. This parameter can be one of the following values defined in WinNT.h. Use NO_SUBGROUP_GUID to refer to the default power scheme.</param>
/// <param name="PowerSettingGuid">The identifier of the power setting</param>
/// <param name="AcValueIndex">The AC value index.</param>
/// <returns>Returns ERROR_SUCCESS (zero) if the call was successful, and a nonzero value if the call failed.</returns>
[DllImport("powrprof.dll", EntryPoint = "PowerWriteACValueIndex", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint PowerWriteACValueIndex(IntPtr RootPowerKey, ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, uint AcValueIndex);

/// <summary>
/// Retrieves the AC index of the specified power setting.
/// </summary>
/// <param name="RootPowerKey">This parameter is reserved for future use and must be set to NULL.</param>
/// <param name="SchemeGuid">The identifier of the power scheme.</param>
/// <param name="SubGroupOfPowerSettingsGuid">The subgroup of power settings. This parameter can be one of the following values defined in WinNT.h. Use NO_SUBGROUP_GUID to refer to the default power scheme.</param>
/// <param name="PowerSettingGuid">The identifier of the power setting</param>
/// <param name="AcValueIndex">A pointer to a variable that receives the AC value index.</param>
/// <returns>Returns ERROR_SUCCESS (zero) if the call was successful, and a nonzero value if the call failed.</returns>
[DllImport("powrprof.dll", EntryPoint = "PowerReadDCValueIndex", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint PowerReadDCValueIndex(IntPtr RootPowerKey, ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, out uint AcValueIndex);


/// <summary>
/// Sets the AC value index of the specified power setting.
/// </summary>
/// <param name="RootPowerKey">This parameter is reserved for future use and must be set to NULL.</param>
/// <param name="SchemeGuid">The identifier of the power scheme.</param>
/// <param name="SubGroupOfPowerSettingsGuid">The subgroup of power settings. This parameter can be one of the following values defined in WinNT.h. Use NO_SUBGROUP_GUID to refer to the default power scheme.</param>
/// <param name="PowerSettingGuid">The identifier of the power setting</param>
/// <param name="AcValueIndex">The AC value index.</param>
/// <returns>Returns ERROR_SUCCESS (zero) if the call was successful, and a nonzero value if the call failed.</returns>
[DllImport("powrprof.dll", EntryPoint = "PowerWriteDCValueIndex", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint PowerWriteDCValueIndex(IntPtr RootPowerKey, ref Guid SchemeGuid, ref Guid SubGroupOfPowerSettingsGuid, ref Guid PowerSettingGuid, uint AcValueIndex);


/// <summary>
/// Sets the active power scheme for the current user.
/// </summary>
/// <param name="UserRootPowerKey">This parameter is reserved for future use and must be set to NULL.</param>
/// <param name="SchemeGuid">The identifier of the power scheme.</param>
/// <returns>Returns ERROR_SUCCESS (zero) if the call was successful, and a nonzero value if the call failed.</returns>
[DllImport("powrprof.dll", EntryPoint = "PowerSetActiveScheme", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint PowerSetActiveScheme(IntPtr UserRootPowerKey, ref Guid SchemeGuid);

#endregion

明るさの変更

powercfg

API一覧からも分かるように、Vista以降の電源管理ではAC接続時とバッテリ駆動時で呼び出すAPIが違う上、さらに電源プロファイル(バランス、高パフォーマンス、省電力など)という区分がある。
したがって、これらのAPIを呼び出す際には、項目の設定値のほかに3つのGUIDキーを指定する必要がある。

<SCHEME_GUID>
設定を反映させる電源プロファイルのGUID
powercfg.exe -l で一覧が取得できるので、メモしておく。以下、<SCHEME_GUID>の部分は、この値に置き換えること。
<SUB_GUID>
電源設定のサブカテゴリのGUID
今回は、ディスプレイ関係の電源管理サブグループ(7516b95f-f776-4464-8c53-06167f40cc99)となる。
<SETTING_GUID>
個々の電源設定のGUID
今回は、ディスプレイの明るさ(7516b95f-f776-4464-8c53-06167f40cc99)となる。
これらの値はpowercfgコマンドを使って簡単に調べることが出来るので、今回はこれで代用。*2
powercfg /q <SCHEME_GUID> <SUB_GUID>で確認すると分かるように、ディスプレイの明るさは0~100で指定できることが分かる。
(値は16進数で表示されているので、0x64 = 100である)
>powercfg -q <SCHEME_GUID> 7516b95f-f776-4464-8c53-06167f40cc99


電源設定の GUID: <SCHEME_GUID>  (My Power Management Policy)
  サブグループの GUID: 7516b95f-f776-4464-8c53-06167f40cc99  (ディスプレイ)

  ...(略)...

    電源設定の GUID: aded5e82-b909-4619-9949-f5d71dac0bcb  (ディスプレイの明るさ)
      利用可能な設定の最小値: 0x00000000
      利用可能な設定の最大値: 0x00000064
      利用可能な設定の増分: 0x00000001
      利用可能な設定の単位: %
    現在の AC 電源設定のインデックス: 0x00000064
    現在の DC 電源設定のインデックス: 0x00000021

  ...(略)...
powercfgで変更する場合は、以下のように。-setactiveをしないと、実際に画面の明るさが変更しないことに注意。これは後々のAPI呼び出しの場合も同様である。
>powercfg -setacvalueindex <SCHEME_GUID> 7516b95f-f776-4464-8c53-06167f40cc99 aded5e82-b909-4619-9949-f5d71dac0bcb 50

>powercfg -setactive <SCHEME_GUID>
画面の明るさがちょうど中間になったはず。

API

では、本題のAPI呼び出しでの変更。
Guid MY_POWER_MANAGEMENT_POLICY = new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); // 今回は<SCHEME_GUID>をベタ指定
Guid GUID_VIDEO_SUBGROUP = new Guid("7516b95f-f776-4464-8c53-06167f40cc99");
Guid DisplayBrightnessGuid = new Guid("aded5e82-b909-4619-9949-f5d71dac0bcb");

bool SetACBrightness(int bright_ac)
	if(bright_ac < 0 || bright_ac > 100)
		return false;

	uint ret = PowerWriteACValueIndex(IntPtr.Zero, ref MY_POWER_MANAGEMENT_POLICY, ref GUID_VIDEO_SUBGROUP, ref DisplayBrightnessGuid, (uint)bright_ac);
	if(ret != 0)
		return false;

	ret = PowerSetActiveScheme(IntPtr.Zero, ref MY_POWER_MANAGEMENT_POLICY);
	if(ret != 0)
		return false;

	return true;
}
ちなみに、電源接続時とバッテリ駆動時で呼び出すAPIが異なるので、以下のようにして電源状態を取得して分岐すると良いかな。
System.Windows.Forms.PowerLineStatus ps = System.Windows.Forms.SystemInformation.PowerStatus.PowerLineStatus;
switch(ps) {
	case System.Windows.Forms.PowerLineStatus.Online:
		// AC
		SetACBrightness(value);
		break;

	case System.Windows.Forms.PowerLineStatus.Offline:
		// DC
		SetDCBrightness(value);
		break;
}

*2 : ちゃんと作りこむなら、現在の電源プロファイルのGUIDをきちんと取得する必要があると思うが、ここでは割愛…