views:

122

answers:

3

Hello.

I Delphi, I need a function which determinates if the system menu (resp. window menu, the menu that appears when the icon is clicked) is opened. The reason is that I am writing a anti-keylogger functionality which sends garbage to the current active editcontrol (this also prevents keylogger which read WinAPI messages to read the content). But if system-menu is opened, the editcontrol STILL has the focus, so the garbage will invoke shortcuts.

If I use message WM_INITMENUPOPUP in my TForm1, I can determinate when the system menu opens, but I wish that I do not have to change the TForm, since I want to write a non visual component, which does not need any modifications at the TForm-derivate-class itself.

//I do not want that solution since I have to modify TForm1 for that!
procedure TForm1.WMInitMenuPopup(var Message: TWMInitMenuPopup);  
begin  
 if message.MenuPopup=getsystemmenu(Handle, False) then  
 begin  
  SystemMenuIsOpened := true;  
 end;  
end;

TApplicaton.HookMainWindow() does not send the WM_INITMENUPOPUP to my hook function.

function TForm1.MessageHook(var Msg: TMessage): Boolean;  
begin  
Result := False;  
if (Msg.Msg = WM_INITMENUPOPUP) then  
begin  
// Msg.Msg IS NEVER WM_INITMENUPOPUP!  
 if LongBool(msg.LParamHi) then  
 begin  
  SystemMenuIsOpened := true;  
 end;  
end;  
end;  

procedure TForm1.FormCreate(Sender: TObject);  
begin  
 Application.HookMainWindow(MessageHook);  
end;  

procedure TForm1.FormDestroy(Sender: TObject);  
begin  
  Application.UnhookMainWindow(MessageHook);  
end;

Even after very long research I did not found any information about how to query if the system-menu is opened or not. I do not find any way to determinate the opening+closing of that menu.

Has someone a solution for me please?

Regards
Daniel Marschall

A: 

Not tried this myself, but give this a shot:

Use GetMenuItemRect to get the rect for item 0 of the menu returned by GetSystemMenu. I (assume!) GetMenuItemRect should return 0 if the system menu is not open (because system could not know the rect of the menu item unless it is open?) If the result is non-zero, check if the coords returned are possible for the given screen resolution.

If you have the time, you could look into AutoHotKey's source code to see how to monitor when system menu is open/closed.

Zabba
Alas, GetMenuItemRect() does not work. The system menu of TApplication.Handle is at a fixed position and the system menu of Form1.Handle is at the point where I expect it. (When I move the form, the coordinates change too) But the coordinates are also there when the system menu is invisible. I assume that the function succeeds because the system menu is always created and is only toggled visible/invisible when invoked.
Daniel Marschall
A: 

If you don't want any modifications to TForm-derivate-class, why don't try pure Windows API way to implement your current solution, that is, use SetWindowLongPtr() to intercept the WM_INITMENUPOPUP message. Delphi VCL style to intercept messages is just a wrapper of this Windows API function actually.

For that purpose, use SetWindowLongPtr() to set a new address for the window procedure and to get the original address of the window procedure, both at one blow. Remember to store the original address in a LONG_PTR variable. In 32-bit Delphi, LONG_PTR was Longint; supposing 64-bit Delphi will have been released in the future, LONG_PTR should be Int64; you can use $IFDEF directive to distinguish them as follows:

  Type
    {$IFDEF WIN32}
    PtrInt = Longint;
    {$ELSE}
    PtrInt = Int64;
    {$ENDIF}
    LONG_PTR = PtrInt;

The value for nIndex parameter to be used for this purpose is GWLP_WNDPROC. Also, pass the new address for the window procedure to dwNewLong parameter, e.g. LONG_PTR(NewWndProc). The NewWndProc is a WindowProc Callback Function that processes messages, it is where your put your intercept criteria and override the default handling of the message you are going to intercept. The callback function can be any name, but the parameters must follow the WindowProc convention.

Note that you must call CallWindowProc() to pass any messages not processed by the new window procedure to the original window procedure.

Finally, you should call SetWindowLongPtr() again somewhere in your code to set the address of modified/new window procedure handler back to the original address. The original address has been saved before as mentioned above.

There was a Delphi code example here. It used SetWindowLong(), but now Microsoft recommends to use SetWindowLongPtr() instead to make it compatible with both 32-bit and 64-bit versions of Windows.

SetWindowLongPtr() didn't exist in Windows.pas of Delphi prior to Delphi 2009. If you use an older version of Delphi, you must declare it by yourself, or use JwaWinUser unit of JEDI API Library.

Vantomex
I never heared about that function and I do not know how this can help me to hook WM_INITMENUPOPUP.
Daniel Marschall
Passes `GWLP_WNDPROC` to `nIndex` parameter. <a href="http://www.swissdelphicenter.ch/torry/showcode.php?id=1079">Here</a> as an example. It was used SetWindowLong(), but now Microsoft recommends to use <a href="http://msdn.microsoft.com/en-us/library/ms644898%28VS.85%29.aspx">SetWindowLongPtr()</a> instead to make it compatible with both 32-bit and 64-bit versions of Windows.
Vantomex
The style is different here from VCL style, you must provide a callback function, and it is where your put your intercept criteria and do further processing. Delphi VCL style to intercept message is just a wrapper of this Windows API function actually.
Vantomex
@Vantomex: you can edit your answer to add the extra information you just provided in comments. It makes the answers much more usable and avoids forcing people to scan the comments as well. Even people with a reputation of 1 can edit their **own** answers.
Marjan Venema
I am currently trying to use that function. I hope that it will not block other message-handlers from reacting to that message. My component should work parallel on any other functionality/Form. Do you have any code samples?
Daniel Marschall
It works now. I could now "insert" my handler between the wndprocs.
Daniel Marschall
I has added the `LONG_PTR` issue in the answer.
Vantomex
Thanks. The definiton of LONG_PTR and your edit helped much. I hope that my solution is now a good one. Do you think it is a problem that "unregistering" has to be in same order than "registering" and must not be mixed? (When I "unregister" my hook, I set the LongPtr to the value that it had while "registering". But it might have changed in time between...)
Daniel Marschall
How many forms you hook are? One `SetWindowLongPtr()` is for one window procedure. Supposing you hook many forms, unregistering the hooks does not have to be the same order as registering. If the functionality of the hooks are needed as long as your program are running, you could unregister the hooks in `MainForm.Destroy` event.
Vantomex
A: 

Application.HookMainWindow doesn't do what you seem to think. It hooks the hidden application window, not the main form. To intercept WM_INITMENUPOPUP on a specific form, all you need to do is write a handler for it, as you have seen.

To do this generically for any owner form of a component, you could assign WindowProc property of the form to place the hook:

unit FormHook;

interface

uses
  Windows, Classes, SysUtils, Messages, Controls, Forms;

type
  TFormMessageEvent = procedure(var Message: TMessage; var Handled: Boolean) of object;

  TFormHook = class(TComponent)
  private
    FForm: TCustomForm;
    FFormWindowProc: TWndMethod;
    FOnFormMessage: TFormMessageEvent;
  protected
    procedure FormWindowProc(var Message: TMessage); virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property OnFormMessage: TFormMessageEvent read FOnFormMessage write FOnFormMessage;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Test', [TFormHook]);
end;

procedure TFormHook.FormWindowProc(var Message: TMessage);
var
  Handled: Boolean;
begin
  if Assigned(FFormWindowProc) then
  begin
    Handled := False;

    if Assigned(FOnFormMessage) then
      FOnFormMessage(Message, Handled);

    if not Handled then
      FFormWindowProc(Message);
  end;
end;

constructor TFormHook.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FFormWindowProc := nil;
  FForm := nil;
  while Assigned(AOwner) do
  begin
    if AOwner is TCustomForm then
    begin
      FForm := TCustomForm(AOwner);
      FFormWindowProc := FForm.WindowProc;
      FForm.WindowProc := FormWindowProc;
      Break;
    end;
    AOwner := AOwner.Owner;
  end;
end;

destructor TFormHook.Destroy;
begin
  if Assigned(FForm) and Assigned(FFormWindowProc) then
  begin
    FForm.WindowProc := FFormWindowProc;
    FFormWindowProc := nil;
    FForm := nil;
  end;
  inherited Destroy;
end;

end.

You could then use this component on a form:

procedure TForm1.FormHook1FormMessage(var Message: TMessage; var Handled: Boolean);
begin
  case Message.Msg of
    WM_INITMENUPOPUP:
      ...
  end;
end;

The problem might be that if the form has any other components which do the same thing then you need to make sure that unhooking happens in reverse order (last hooked, last unhooked). The above example hooks in the constructor and unhooks in the destructor; this seems to work even with multiple instances on the same form.

TOndrej
Thanks for that code. This seems to work too. But is overwriting the "WindowProc" a good method? I could imagine that if multiple components overwrite the WindowProc, only the latest defined will work. That would be a great disadvantage. Currently I use SetWindowLongPtr(). When I register the component, I change the WndProc CB and at unregistering I set the CB back to its previous value. (which might be changed in time between :-/ )
Daniel Marschall
WindowProc is just another (arguably, easier) way to replace the window procedure. Either way, if multiple components do this you need to ensure proper order of unhooking.
TOndrej
How can I assure that the unhooking is in same order? I am concerned about this in the SetWindowLongPtr() solution too. Since I want to write a VCL, it is dangerous that the components can be unloaded in a different order than they were created.
Daniel Marschall
Since you can't control what other components will be installed and used on the target form, I'm afraid the best you can do is document your component to warn the user of the implications.
TOndrej