I've been beating my head for over a day now, going through tons of resources trying to figure out how to receive the WM_POWERBROADCAST
Windows message from within a thread.
Currently, I am using AllocateHWnd(WndMethod)
inside of a stand-alone component. When I create an instance of said component in a standard VCL Forms Application, everything works fine, and I receive the WM_POWERBROADCAST
message every time, as needed.
However, when I create an instance of the very same component from within a TThread
, I'm no longer receiving this particular Windows message. I'm receiving all kinds of other messages, just not this particular one.
In my searching for a solution, I've found many resources related to how a Windows Service requires some extra work in order to receive this message. But I'm not using a service, at least not yet. I've also found a couple people mention that a thread needs to have a message loop in order to receive this message, and I've implemented a thread from another answer here, but again, I never receive this particular message.
Below is the complete component how I'm receiving this message, which again works perfectly if this is in a VCL application's main thread. I'm guessing the main thread needs to receive this message and forward it into the thread.
How do I make this receive the WM_POWERBROADCAST
message when inside of a TThread
?
unit JD.Power.Monitor;
(*
JD Power Monitor
by Jerry Dodge
Purpose: To monitor the current state of power on the computer, and trigger
events when different power related changes occur.
Component: TPowerMonitor
- Create an instance of TPowerMonitor component
- Choose desired power settings to get notified of using Settings property
- Implement event handlers for those events you wish to monitor
- Component automatically takes care of the rest of the work
*)
interface
uses
System.Classes, System.SysUtils, System.Generics.Collections,
Winapi.ActiveX, Winapi.Windows, Winapi.Messages;
type
TPowerSetting = (psACDCPowerSource, psBatteryPercentage,
psConsoleDisplayState, psGlobalUserPresence, psIdleBackgroundTask,
psMonitorPower, psPowerSaving, psPowerSchemePersonality,
psSessionDisplayStatus, psSessionUserPresence, psSystemAwayMode);
TPowerSettings = set of TPowerSetting;
TPowerSource = (poAC, poDC, poHot);
TPowerDisplayState = (pdOff, pdOn, pdDimmed);
TPowerUserPresence = (puPresent = 0, puInactive = 2);
TPowerSavingStatus = (psSaverOff, psSaverOn);
TPowerAwayMode = (paExiting, paEntering);
TPowerPersonality = (ppHighPerformance, ppPowerSaver, ppAutomatic);
TPowerMonitorSettingHandles = array[TPowerSetting] of HPOWERNOTIFY;
TPowerQueryEndSessionEvent = procedure(Sender: TObject; var EndSession: Boolean) of object;
TPowerEndSessionEvent = procedure(Sender: TObject) of object;
TPowerSettingSourceChangeEvent = procedure(Sender: TObject;
const Src: TPowerSource) of object;
TPowerSettingBatteryPercentEvent = procedure(Sender: TObject;
const Perc: Single) of object;
TPowerSettingDisplayStateEvent = procedure(Sender: TObject;
const State: TPowerDisplayState) of object;
TPowerSettingUserPresenceEvent = procedure(Sender: TObject;
const Presence: TPowerUserPresence) of object;
TPowerSettingSavingEvent = procedure(Sender: TObject;
const Status: TPowerSavingStatus) of object;
TPowerAwayModeEvent = procedure(Sender: TObject;
const Mode: TPowerAwayMode) of object;
TPowerPersonalityEvent = procedure(Sender: TObject;
const Personality: TPowerPersonality) of object;
TPowerMonitor = class(TComponent)
private
FHandle: HWND;
FSettingHandles: TPowerMonitorSettingHandles;
FSettings: TPowerSettings;
FBatteryPresent: Boolean;
FOnQueryEndSession: TPowerQueryEndSessionEvent;
FOnEndSession: TPowerEndSessionEvent;
FOnPowerStatusChange: TNotifyEvent;
FOnResumeAutomatic: TNotifyEvent;
FOnResumeSuspend: TNotifyEvent;
FOnSuspend: TNotifyEvent;
FOnSourceChange: TPowerSettingSourceChangeEvent;
FOnBatteryPercent: TPowerSettingBatteryPercentEvent;
FOnConsoleDisplayState: TPowerSettingDisplayStateEvent;
FOnGlobalUserPresence: TPowerSettingUserPresenceEvent;
FOnIdleBackgroundTask: TNotifyEvent;
FOnMonitorPower: TPowerSettingDisplayStateEvent;
FOnPowerSavingStatus: TPowerSettingSavingEvent;
FOnSessionDisplayState: TPowerSettingDisplayStateEvent;
FOnSessionUserPresence: TPowerSettingUserPresenceEvent;
FOnAwayMode: TPowerAwayModeEvent;
FOnPersonality: TPowerPersonalityEvent;
procedure UnregisterSettings;
procedure RegisterSettings;
procedure SetSettings(const Value: TPowerSettings);
protected
procedure HandlePowerSetting(const Val: PPowerBroadcastSetting);
procedure WndMethod(var Msg: TMessage);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Settings: TPowerSettings read FSettings write SetSettings;
property OnQueryEndSession: TPowerQueryEndSessionEvent
read FOnQueryEndSession write FOnQueryEndSession;
property OnEndSession: TPowerEndSessionEvent
read FOnEndSession write FOnEndSession;
property OnPowerStatusChange: TNotifyEvent
read FOnPowerStatusChange write FOnPowerStatusChange;
property OnResumeAutomatic: TNotifyEvent
read FOnResumeAutomatic write FOnResumeAutomatic;
property OnResumeSuspend: TNotifyEvent
read FOnResumeSuspend write FOnResumeSuspend;
property OnSuspend: TNotifyEvent
read FOnSuspend write FOnSuspend;
property OnSourceChange: TPowerSettingSourceChangeEvent
read FOnSourceChange write FOnSourceChange;
property OnBatteryPercent: TPowerSettingBatteryPercentEvent
read FOnBatteryPercent write FOnBatteryPercent;
property OnConsoleDisplayState: TPowerSettingDisplayStateEvent
read FOnConsoleDisplayState write FOnConsoleDisplayState;
property OnGlobalUserPresence: TPowerSettingUserPresenceEvent
read FOnGlobalUserPresence write FOnGlobalUserPresence;
property OnIdleBackgroundTask: TNotifyEvent
read FOnIdleBackgroundTask write FOnIdleBackgroundTask;
property OnMonitorPower: TPowerSettingDisplayStateEvent
read FOnMonitorPower write FOnMonitorPower;
property OnPowerSavingStatus: TPowerSettingSavingEvent
read FOnPowerSavingStatus write FOnPowerSavingStatus;
property OnSessionDisplayState: TPowerSettingDisplayStateEvent
read FOnSessionDisplayState write FOnSessionDisplayState;
property OnSessionUserPresence: TPowerSettingUserPresenceEvent
read FOnSessionUserPresence write FOnSessionUserPresence;
property OnAwayMode: TPowerAwayModeEvent
read FOnAwayMode write FOnAwayMode;
property OnPersonality: TPowerPersonalityEvent
read FOnPersonality write FOnPersonality;
end;
implementation
{ TPowerMonitor }
constructor TPowerMonitor.Create(AOwner: TComponent);
begin
inherited;
FBatteryPresent:= False;
FHandle := AllocateHWnd(WndMethod);
end;
destructor TPowerMonitor.Destroy;
begin
UnregisterSettings;
DeallocateHWnd(FHandle);
inherited;
end;
procedure TPowerMonitor.SetSettings(const Value: TPowerSettings);
begin
UnregisterSettings;
FSettings := Value;
RegisterSettings;
end;
procedure TPowerMonitor.WndMethod(var Msg: TMessage);
var
Handled: Boolean;
begin
Handled := True;
case Msg.Msg of
WM_POWERBROADCAST: begin
//TODO: Why is this never received when inside of a thread?
case Msg.WParam of
PBT_APMPOWERSTATUSCHANGE: begin
//Power status has changed.
if Assigned(FOnPowerStatusChange) then
FOnPowerStatusChange(Self);
end;
PBT_APMRESUMEAUTOMATIC: begin
//Operation is resuming automatically from a low-power state.
//This message is sent every time the system resumes.
if Assigned(FOnResumeAutomatic) then
FOnResumeAutomatic(Self);
end;
PBT_APMRESUMESUSPEND: begin
//Operation is resuming from a low-power state. This message
//is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered
//by user input, such as pressing a key.
if Assigned(FOnResumeSuspend) then
FOnResumeSuspend(Self);
end;
PBT_APMSUSPEND: begin
//System is suspending operation.
if Assigned(FOnSuspend) then
FOnSuspend(Self);
end;
PBT_POWERSETTINGCHANGE: begin
//A power setting change event has been received.
HandlePowerSetting(PPowerBroadcastSetting(Msg.LParam));
end;
else begin
end;
end;
end
else Handled := False;
end;
if Handled then
Msg.Result := 0
else
Msg.Result := DefWindowProc(FHandle, Msg.Msg,
Msg.WParam, Msg.LParam);
end;
procedure TPowerMonitor.HandlePowerSetting(const Val: PPowerBroadcastSetting);
var
Pers: TPowerPersonality;
function ValAsDWORD: DWORD;
begin
Result:= DWORD(Val.Data[0]);
end;
function ValAsGUID: TGUID;
begin
Result:= StringToGUID('{00000000-0000-0000-0000-000000000000}'); //Default
if SizeOf(TGUID) = Val.DataLength then begin
Move(Val.Data, Result, Val.DataLength);
end;
end;
function IsVal(G: String): Boolean;
begin
Result:= Assigned(Val);
if Result then
Result:= IsEqualGUID(StringToGUID(G), Val.PowerSetting);
end;
function IsValGuid(G: String): Boolean;
begin
Result:= Assigned(Val);
if Result then
Result:= IsEqualGUID(StringToGUID(G), ValAsGUID);
end;
begin
if IsVal('{5d3e9a59-e9D5-4b00-a6bd-ff34ff516548}') then begin
//GUID_ACDC_POWER_SOURCE
if Assigned(FOnSourceChange) then
FOnSourceChange(Self, TPowerSource(ValAsDWORD));
end else
if IsVal('{a7ad8041-b45a-4cae-87a3-eecbb468a9e1}') then begin
//GUID_BATTERY_PERCENTAGE_REMAINING
//We assume that if we get this message, that there is a battery connected.
//Otherwise if this never occurs, then a battery is not present.
//TODO: How to handle if battery is detached and no longer present?
FBatteryPresent:= True;
if Assigned(FOnBatteryPercent) then
FOnBatteryPercent(Self, ValAsDWORD);
end else
if IsVal('{6fe69556-704a-47a0-8f24-c28d936fda47}') then begin
//GUID_CONSOLE_DISPLAY_STATE
if Assigned(FOnConsoleDisplayState) then
FOnConsoleDisplayState(Self, TPowerDisplayState(ValAsDWORD));
end else
if IsVal('{786E8A1D-B427-4344-9207-09E70BDCBEA9}') then begin
//GUID_GLOBAL_USER_PRESENCE
if Assigned(FOnGlobalUserPresence) then
FOnGlobalUserPresence(Self, TPowerUserPresence(ValAsDWORD));
end else
if IsVal('{515c31d8-f734-163d-a0fd-11a08c91e8f1}') then begin
//GUID_IDLE_BACKGROUND_TASK
if Assigned(FOnIdleBackgroundTask) then
FOnIdleBackgroundTask(Self);
end else
if IsVal('{02731015-4510-4526-99e6-e5a17ebd1aea}') then begin
//GUID_MONITOR_POWER_ON
if Assigned(FOnMonitorPower) then
FOnMonitorPower(Self, TPowerDisplayState(ValAsDWORD));
end else
if IsVal('{E00958C0-C213-4ACE-AC77-FECCED2EEEA5}') then begin
//GUID_POWER_SAVING_STATUS
if Assigned(FOnPowerSavingStatus) then
FOnPowerSavingStatus(Self, TPowerSavingStatus(ValAsDWORD));
end else
if IsVal('{245d8541-3943-4422-b025-13A784F679B7}') then begin
//GUID_POWERSCHEME_PERSONALITY
if IsValGuid('{8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c}') then begin
Pers:= TPowerPersonality.ppHighPerformance;
end else
if IsValGuid('{a1841308-3541-4fab-bc81-f71556f20b4a}') then begin
Pers:= TPowerPersonality.ppPowerSaver;
end else
if IsValGuid('{381b4222-f694-41f0-9685-ff5bb260df2e}') then begin
Pers:= TPowerPersonality.ppAutomatic;
end else begin
//TODO: Handle unrecognized GUID
Pers:= TPowerPersonality.ppAutomatic;
end;
if Assigned(FOnPersonality) then
FOnPersonality(Self, Pers);
end else
if IsVal('{2B84C20E-AD23-4ddf-93DB-05FFBD7EFCA5}') then begin
//GUID_SESSION_DISPLAY_STATUS
if Assigned(FOnSessionDisplayState) then
FOnSessionDisplayState(Self, TPowerDisplayState(ValAsDWORD));
end else
if IsVal('{3C0F4548-C03F-4c4d-B9F2-237EDE686376}') then begin
//GUID_SESSION_USER_PRESENCE
if Assigned(FOnSessionUserPresence) then
FOnSessionUserPresence(Self, TPowerUserPresence(ValAsDWORD));
end else
if IsVal('{98a7f580-01f7-48aa-9c0f-44352c29e5C0}') then begin
//GUID_SYSTEM_AWAYMODE
if Assigned(FOnAwayMode) then
FOnAwayMode(Self, TPowerAwayMode(ValAsDWORD));
end else begin
//TODO: Handle Unrecognized GUID
end;
end;
function PowerSettingGUID(const Setting: TPowerSetting): TGUID;
begin
case Setting of
psACDCPowerSource: Result:= StringToGUID('{5d3e9a59-e9D5-4b00-a6bd-ff34ff516548}');
psBatteryPercentage: Result:= StringToGUID('{a7ad8041-b45a-4cae-87a3-eecbb468a9e1}');
psConsoleDisplayState: Result:= StringToGUID('{6fe69556-704a-47a0-8f24-c28d936fda47}');
psGlobalUserPresence: Result:= StringToGUID('{786E8A1D-B427-4344-9207-09E70BDCBEA9}');
psIdleBackgroundTask: Result:= StringToGUID('{515c31d8-f734-163d-a0fd-11a08c91e8f1}');
psMonitorPower: Result:= StringToGUID('{02731015-4510-4526-99e6-e5a17ebd1aea}');
psPowerSaving: Result:= StringToGUID('{E00958C0-C213-4ACE-AC77-FECCED2EEEA5}');
psPowerSchemePersonality: Result:= StringToGUID('{245d8541-3943-4422-b025-13A784F679B7}');
psSessionDisplayStatus: Result:= StringToGUID('{2B84C20E-AD23-4ddf-93DB-05FFBD7EFCA5}');
psSessionUserPresence: Result:= StringToGUID('{3C0F4548-C03F-4c4d-B9F2-237EDE686376}');
psSystemAwayMode: Result:= StringToGUID('{98a7f580-01f7-48aa-9c0f-44352c29e5C0}');
end;
end;
procedure TPowerMonitor.RegisterSettings;
var
V: TPowerSetting;
begin
for V := Low(TPowerSetting) to High(TPowerSetting) do begin
if V in FSettings then begin
FSettingHandles[V]:= RegisterPowerSettingNotification(FHandle,
PowerSettingGUID(V), 0);
end;
end;
end;
procedure TPowerMonitor.UnregisterSettings;
var
V: TPowerSetting;
begin
for V := Low(TPowerSetting) to High(TPowerSetting) do begin
if V in FSettings then begin
UnregisterPowerSettingNotification(FSettingHandles[V]);
end;
end;
end;
end.
Further, based on the other answer as mentioned, here's how I'm attempting to capture this message using only a thread, although I'm sure this is not actually what I need since WM_POWERBROADCAST
is not a posted message:
unit JD.ThreadTest;
interface
uses
System.Classes, Winapi.Messages, Winapi.Windows;
type
TDataThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
procedure HandlePower(AMsg: TMsg);
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(const Title: String); reintroduce;
end;
implementation
constructor TDataThread.Create(const Title: String);
begin
inherited Create(True);
FTitle := Title;
with FWndClass do begin
Style := 0;
lpfnWndProc := @DefWindowProc;
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := HInstance;
hIcon := 0;
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW;
lpszMenuName := nil;
lpszClassName := PChar(Self.ClassName);
end;
end;
procedure TDataThread.Execute;
var
Msg: TMsg;
begin
if Winapi.Windows.RegisterClass(FWndClass) = 0 then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME,
0, 0, 698, 517, 0, 0, HInstance, nil);
if FWnd = 0 then Exit;
while GetMessage(Msg, FWnd, 0, 0) = True do begin
if Terminated then Break;
case Msg.message of
WM_POWERBROADCAST: begin
HandlePower(Msg); //Never receives this message
end;
else begin
TranslateMessage(msg);
DispatchMessage(msg)
end;
end;
end;
end;
procedure TDataThread.HandlePower(AMsg: TMsg);
begin
end;
procedure TDataThread.DoTerminate;
begin
if FWnd <> 0 then DestroyWindow(FWnd);
Winapi.Windows.UnregisterClass(PChar(Self.ClassName), FWndClass.hInstance);
inherited;
end;
end.
PS: The end goal is to make this component re-usable, and to be using it inside of a thread, which will be spawned inside of a service.
EDIT
Just to show some perspective, here's a screenshot of my results, when I put my computer into sleep mode. The form on the left is my 100% working UI application, without any worker thread. It receives many messages through the WM_POWERBROADCAST
message. The one on the right is where I attempt to capture this message inside of a thread - updated with the code below in Remy's answer.
Obviously the "Power Setting" specific messages are not received, because I haven't called RegisterPowerSettingNotification
- but there are other cases when I should still receive this message regardless, such as PBT_APMSUSPEND
.
unit JD.ThreadTest;
interface
uses
System.Classes, System.SysUtils, Winapi.Messages, Winapi.Windows;
type
TMessageEvent = procedure(Sender: TObject; Message: TMessage) of object;
TDataThread = class(TThread)
private
FTitle: String;
FWnd: HWND;
FWndClass: WNDCLASS;
FOnMessage: TMessageEvent;
FMsg: TMessage;
procedure HandleMessage(var Message: TMessage);
protected
procedure Execute; override;
procedure DoTerminate; override;
procedure DoOnMessage;
public
constructor Create(const Title: String); reintroduce;
property OnMessage: TMessageEvent read FOnMessage write FOnMessage;
end;
implementation
function DataThreadWndProc(Wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
Thread: TDataThread;
Message: TMessage;
begin
if Msg = WM_NCCREATE then
begin
Thread := TDataThread(PCREATESTRUCT(lParam)^.lpCreateParams);
SetWindowLongPtr(Wnd, GWLP_USERDATA, LONG_PTR(Thread));
end else
Thread := TDataThread(GetWindowLongPtr(Wnd, GWLP_USERDATA));
if Thread <> nil then
begin
Message.Msg := Msg;
Message.WParam := wParam;
Message.LParam := lParam;
Message.Result := 0;
Thread.HandleMessage(Message);
Result := Message.Result;
end else
Result := DefWindowProc(Wnd, Msg, wParam, lParam);
end;
constructor TDataThread.Create(const Title: String);
begin
inherited Create(True);
FTitle := Title;
with FWndClass do
begin
Style := 0;
lpfnWndProc := @DataThreadWndProc;
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := HInstance;
hIcon := 0;
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW;
lpszMenuName := nil;
lpszClassName := 'TDataThread';
end;
end;
procedure TDataThread.Execute;
var
Msg: TMsg;
begin
if Winapi.Windows.RegisterClass(FWndClass) = 0 then Exit;
FWnd := CreateWindow(FWndClass.lpszClassName, PChar(FTitle), WS_DLGFRAME, 0, 0, 698, 517, 0, 0, HInstance, Self);
if FWnd = 0 then Exit;
while GetMessage(Msg, 0, 0, 0) do
begin
if Terminated then Exit;
TranslateMessage(msg);
DispatchMessage(msg);
end;
end;
procedure TDataThread.DoOnMessage;
begin
if Assigned(FOnMessage) then
FOnMessage(Self, FMsg);
end;
procedure TDataThread.DoTerminate;
begin
if FWnd <> 0 then DestroyWindow(FWnd);
Winapi.Windows.UnregisterClass(FWndClass.lpszClassName, HInstance);
inherited;
end;
procedure TDataThread.HandleMessage(var Message: TMessage);
begin
FMsg:= Message;
Synchronize(DoOnMessage);
case Message.Msg of
WM_POWERBROADCAST:
begin
end;
else
Message.Result := DefWindowProc(FWnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
end.