Detect when user locks/unlocks screen in Windows 7 with Delphi
Asked Answered
S

1

9

How to detect when the user locks/unlocks the screen in Windows 7?

I found this question which has an answer for C#, but I'd like to use it in Delphi 2009. I'd guess there is some windows message (like these) which could do the work. This is the code I tried, but it didn't work:

const
  NOTIFY_FOR_ALL_SESSIONS = 1;
  {$EXTERNALSYM NOTIFY_FOR_ALL_SESSIONS}
  NOTIFY_FOR_THIS_SESSION = 0;
  {$EXTERNALSYM NOTIFY_FOR_THIS_SESSION}

type

TfrmAlisson = class(TForm)
  lbl2: TLabel;
  procedure FormCreate(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
public
  FLockedCount: Integer;
  procedure WndProc(var Message: TMessage); override;
  function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): bool; stdcall;
  function WTSUnRegisterSessionNotification(hWND: HWND): bool; stdcall;
end;

implementation

uses
  // my impl uses here

procedure TfrmAlisson.FormCreate(Sender: TObject);
begin
  if (WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION)) then
    ShowMessage('Nice')
  else
  begin
    lastError := GetLastError;
    ShowMessage(SysErrorMessage(lastError));
  end;
end;

procedure TfrmAlisson.FormDestroy(Sender: TObject);
begin
  WTSUnRegisterSessionNotification(Handle);
end;

procedure TfrmAlisson.WndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_WTSSESSION_CHANGE:
      begin
        if Message.wParam = WTS_SESSION_LOCK then
        begin
          Inc(FLockedCount);
        end;
        if Message.wParam = WTS_SESSION_UNLOCK then
        begin
          lbl2.Caption := 'Session was locked ' +
          IntToStr(FLockedCount) + ' times.';
        end;
      end;
  end;
  inherited;
end;

function TfrmAlisson.WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): bool;
  external 'wtsapi32.dll' Name 'WTSRegisterSessionNotification';

function TfrmAlisson.WTSUnRegisterSessionNotification(hWND: HWND): bool;
  external 'wtsapi32.dll' Name 'WTSUnRegisterSessionNotification';

When FormCreate executes, WTSRegisterSessionNotification returns false and the last OS error returns Invalid Parameter.

Squalor answered 31/8, 2017 at 18:47 Comment(10)
call WTSRegisterSessionNotification and you got WM_WTSSESSION_CHANGE messages. when wParam is WTS_SESSION_LOCK or WTS_SESSION_UNLOCK your casePlacard
@Placard thanks, I'm gonna read and tell you if I manage to achieve it.Squalor
@RbMm: that should be posted as an answer.Mystical
@RemyLebeau - really this duplicate - for example https://mcmap.net/q/1315062/-windows-message-for-user-locking-screenPlacard
@RbMm: then you should have voted to close this question as a duplicateMystical
@RemyLebeau I edited the question. I tried implementing it, but I'm not familiar with winapi, so I don't know if I did something wrong. When I call WTSRegisterSessionNotification, it returns false and if I get last OS error, it returns invalid parameter (in portuguese, in my case).Squalor
You must not pass a VCL window handle. They can be recreated. Use AllocateHWnd. Or the application window.Allenaallenby
@DavidHeffernan: there is nothing wrong with using a VCL window handle, as long as you account for recreation, in this case by overriding the Form's CreateWnd() method.Mystical
Which this code doesn't do, does it. And there's a window during recreation where you aren't listening. So yes, it is wrong to use a VCL window.Allenaallenby
@DavidHeffernan thanks for your advice, it's important pointing everything that could fail. My code is now allocating a dedicated window, as the code suggested by Remy.Squalor
M
12

Your code does not work because you did not implement it correctly.

You are not declaring WTSRegisterSessionNotification() and WTSUnRegisterSessionNotification() correctly.

Also, you are not accounting for the possibility of the VCL ever recreating the Form's window dynamically during the Form object's lifetime. So, even if WTSRegisterSessionNotification() were successful, you can lose your registration and not realize it.

Try this instead:

interface

uses
  ...;

type
  TfrmAlisson = class(TForm)
    lbl2: TLabel;
  protected
    procedure CreateWnd; override;
    procedure DestroyWindowHandle; override;
    procedure WndProc(var Message: TMessage); override;
  public
    LockedCount: Integer;
  end;

implementation

const
  NOTIFY_FOR_THIS_SESSION = $0;
  NOTIFY_FOR_ALL_SESSIONS = $1;

function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSRegisterSessionNotification';
function WTSUnRegisterSessionNotification(hWnd: HWND): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSUnRegisterSessionNotification';

procedure TfrmAlisson.CreateWnd;
begin
  inherited;
  if not WTSRegisterSessionNotification(Handle, NOTIFY_FOR_THIS_SESSION) then
    RaiseLastOSError;
end;

procedure TfrmAlisson.DestroyWindowHandle;
begin
  WTSUnRegisterSessionNotification(Handle);
  inherited;
end;

procedure TfrmAlisson.WndProc(var Message: TMessage);
begin
  if Message.Msg = WM_WTSSESSION_CHANGE then
  begin
    case Message.wParam of
      WTS_SESSION_LOCK: begin
        Inc(LockedCount);
      end;
      WTS_SESSION_UNLOCK: begin
        lbl2.Caption := Format('Session was locked %d times.', [LockedCount]);
      end;
    end;
  end;
  inherited;
end;

end.

That being said, consider writing the code to not rely on the VCL's window recreation behavior. You can allocate a dedicated window for monitoring the session changes:

interface

uses
  ...;

type
  TfrmAlisson = class(TForm)
    lbl2: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    SessionWnd: HWND;
    procedure SessionWndProc(var Message: TMessage);
  public
    LockedCount: Integer;
  end;

implementation

const
  NOTIFY_FOR_THIS_SESSION = $0;
  NOTIFY_FOR_ALL_SESSIONS = $1;

function WTSRegisterSessionNotification(hWnd: HWND; dwFlags: DWORD): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSRegisterSessionNotification';
function WTSUnRegisterSessionNotification(hWnd: HWND): Boolean; stdcall; external 'wtsapi32.dll' name 'WTSUnRegisterSessionNotification';

procedure TfrmAlisson.FormCreate(Sender: TObject);
begin
  SessionWnd := AllocateHWnd(SessionWndProc);
  if not WTSRegisterSessionNotification(SessionWnd, NOTIFY_FOR_THIS_SESSION) then
    RaiseLastOSError;
end;

procedure TfrmAlisson.FormDestroy(Sender: TObject);
begin
  if SessionWnd <> 0 then
  begin
    WTSUnRegisterSessionNotification(SessionWnd);
    DeallocateHWnd(SessionWnd);
  end;
end;

procedure TfrmAlisson.SessionWndProc(var Message: TMessage);
begin
  if Message.Msg = WM_WTSSESSION_CHANGE then
  begin
    case Message.wParam of
      WTS_SESSION_LOCK: begin
        Inc(LockedCount);
      end;
      WTS_SESSION_UNLOCK: begin
        lbl2.Caption := Format('Session was locked %d times.', [LockedCount]);
      end;
    end;
  end;

  Message.Result := DefWindowProc(SessionWnd, Message.Msg, Message.WParam, Message.LParam);
end;

end.
Mystical answered 31/8, 2017 at 20:31 Comment(6)
I'm receiving undeclared identifier: WTSRegisterSessionNotification. I added Windows to interface uses. I forgot to mention I'm compiling with Windows 10, even though the application will be ran by users with Windows 7.Squalor
I redeclared it, following this sample, and it worked with your sample. The only thing I noted is DestroyWnd is not called when I'm debugging and close the application (maybe this is called after the process is detached from debugger).Squalor
@Alisson: my bad, the WTS functions were added to the Windows unit in XE2. As for DestroyWnd(), it is only called when the window is destroyed while the Form remains alive. The VCL bypasses DestroyWnd during the Form's destruction. I have updated my answer to account for these.Mystical
It worked nicely, thank you so much. I changed my code to allocate a dedicated window like suggested.Squalor
FWIW, in CreateWnd and DestroyWindowHandle, it is idiomatic to use WindowHandle rather than Handle.Allenaallenby
@DavidHeffernan: they are the same thing in those contexts. They both return FHandle. The only difference is Handle allocates FHandle if zero, WindowHande doesn't. In those contexts, FHandle is never zero. And I rarely ever see anyone use WindowHandle in those contexts. Either one works.Mystical

© 2022 - 2024 — McMap. All rights reserved.