program ScriptHostZaapLite;

uses
  Windows, Messages, SysUtils,
  U_ScriptHost;

{$R *.RES}

type
  TNotifyIconData = packed record
    cbSize: DWORD;
    Wnd: HWND;
    uID: UINT;
    uFlags: UINT;
    uCallbackMessage: UINT;
    hIcon: HICON;
    szTip: array [0..127] of WideChar;
    dwState: DWORD;
    dwStateMask: DWORD;
    szInfo: array [0..255] of WideChar;
    uTimeout: UINT;
    szInfoTitle: array [0..63] of WideChar;
    dwInfoFlags: DWORD;
  end;
  PNotifyIconData = ^TNotifyIconData;

function Shell_NotifyIcon(dwMessage: DWORD; lpData: PNotifyIconData): BOOL; stdcall; external 'shell32.dll' name 'Shell_NotifyIconW';

const
  WM_ICONTRAY = WM_USER + 1;

  {$EXTERNALSYM NIM_ADD}
  NIM_ADD         = $00000000;
  {$EXTERNALSYM NIM_MODIFY}
  NIM_MODIFY      = $00000001;
  {$EXTERNALSYM NIM_DELETE}
  NIM_DELETE      = $00000002;
  {$EXTERNALSYM NIM_SETFOCUS}
  NIM_SETFOCUS    = $00000003;
  {$EXTERNALSYM NIM_SETVERSION}
  NIM_SETVERSION  = $00000004;

  {$EXTERNALSYM NIF_MESSAGE}
  NIF_MESSAGE     = $00000001;
  {$EXTERNALSYM NIF_ICON}
  NIF_ICON        = $00000002;
  {$EXTERNALSYM NIF_TIP}
  NIF_TIP         = $00000004;
  {$EXTERNALSYM NIF_STATE}
  NIF_STATE       = $00000008;
  {$EXTERNALSYM NIF_INFO}
  NIF_INFO        = $00000010;

  {$EXTERNALSYM NIIF_NONE}
  NIIF_NONE       = $00000000;
  {$EXTERNALSYM NIIF_INFO}
  NIIF_INFO       = $00000001;
  {$EXTERNALSYM NIIF_WARNING}
  NIIF_WARNING    = $00000002;
  {$EXTERNALSYM NIIF_ERROR}
  NIIF_ERROR      = $00000003;
  {$EXTERNALSYM NIIF_ICON_MASK}
  NIIF_ICON_MASK  = $0000000F;

var
  TrayIconData: TNotifyIconData;
  wClass: TWndClass;
  hAppHandle: HWND;
  Msg: TMSG;

{ ---------------------------------------------------------------------------- }
procedure PopupTrayIconMenu();
var
  PopUp: THandle;
  pt: TPoint;
  SelectedID: Cardinal;
  mi: TMenuItemInfo;
begin
  PopUp := CreatePopupMenu;
  try
    mi.cbSize := sizeof(mi);
    mi.fMask := MIIM_ID or MIIM_STATE or MIIM_STRING or MIIM_BITMAP;
    mi.fType := MFT_STRING;
    mi.fState := MFS_ENABLED or MFS_UNHILITE;
    mi.wID := 2;
    mi.dwItemData := 0;
    if ScriptHost.Enabled then begin
      mi.dwTypeData := PChar('Pause');
      mi.cch := 5;
      mi.hbmpItem := HBMMENU_POPUP_MINIMIZE;
    end else begin
      mi.dwTypeData := PChar('Resume');
      mi.cch := 6;
      mi.hbmpItem := HBMMENU_POPUP_RESTORE;
    end;
    InsertMenuItem(PopUp, 0, True, mi);

    mi.cbSize := sizeof(mi);
    mi.fMask := MIIM_ID or MIIM_STATE or MIIM_STRING or MIIM_BITMAP;
    mi.fType := MFT_STRING;
    mi.fState := MFS_ENABLED or MFS_DEFAULT or MFS_HILITE;
    mi.wID := 1;
    mi.dwItemData := 0;
    mi.dwTypeData := PChar('Exit');
    mi.cch := 4;
    mi.hbmpItem := HBMMENU_POPUP_CLOSE;
    InsertMenuItem(PopUp, 1, True, mi);

    GetCursorPos(pt);
    SetForegroundWindow(hAppHandle);
    SelectedID := Cardinal(TrackPopupMenu(PopUp, TPM_RIGHTBUTTON or TPM_RETURNCMD or TPM_NONOTIFY,
                                          pt.x, pt.y, 0, hAppHandle, nil));
    PostMessage(hAppHandle, WM_NULL, 0, 0);
    case SelectedID of
      0: begin
        // the menu was canceled
      end;
      1: begin // Exit
        PostMessage(hAppHandle, WM_CLOSE, 0, 0);
      end;
      2: begin // Pause/Resume
        ScriptHost.Enabled := not ScriptHost.Enabled;
      end
      else begin
        MessageBox(hAppHandle, PChar(Format('Menu id %d', [SelectedID])), wClass.lpszClassName, MB_ICONINFORMATION);
      end;
    end;
  finally
    DestroyMenu(PopUp);
  end;
end{PopupTrayIconMenu};

{ ---------------------------------------------------------------------------- }
function WndMessageProc(hWnd: HWND; Msg: UINT; WParam: WPARAM; LParam: LPARAM): UINT; stdcall;
begin
 {This is the "Window Proc" used to communicate with the OS,
  these messages are how the OS tells this program what has
  happened, look up the 5 WM_ messages in the Win32 API Help.
  This function must send a Result back to Windows for its
  "message" back to the OS, which may change what the OS does
  for that Msg. Each of these messages will produce a MessageBox
  so you can see when the message gets to this function}

 Result := 0;
 case Msg of
    WM_CREATE:
    begin
     {the CreateWindow fuction sends this WM_CREATE to its Class's
      lpfnWndProc, after the window is created, but Before the
      CreateWindow function returns}
      try
        ScriptHost := TScriptHost.Create(IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 'Scripts');
        ScriptHost.Enabled := True;
      except
        on E: Exception do begin
          MessageBox(hAppHandle, PChar(E.Message), PChar(E.ClassName), MB_ICONERROR);
          {if the result is -1 then this new window will be Destroyed before it is
          shown, since this is the MAIN window of this Program a Result of -1 will
          tell the OS to call DestroyWindow(hAppHandle); and end this Program}
          Result := $FFFFFFFF;
          Exit;
          {if you want the Result to be -1 then you MUST Exit here so
           DefWindowProc( ) will NOT be called and change the Result to a 0}
        end;
      end;
    end;

    WM_DESTROY:
    begin
      {WM_DESTROY is sent After the window is hidden but
      before the window is destroyed}
      Shell_NotifyIcon(NIM_DELETE, @TrayIconData);
      FreeAndNil(ScriptHost);
      PostQuitMessage(0);
      {IMPORTANT: to end this Program you need to end your
       GetMessage Loop. To do this you will have to send the
       WM_QUIT message with PostQuitMessage(0)}
    end;

    WM_CLOSE: begin
//     {the WM_CLOSE message is sent to tell your window to CLOSE
//      and since this is the Main window, to Exit this processes}
//        {Unlike WM_CREATE, the Result of the WM_CLOSE
//        message DOES NOT change what the OS does.
//        You must Exit in order to keep DefWindowProc
//        from calling DestroyWindow(hAppHandle);}
//       Exit;
    end;

    WM_CHAR: begin
      {the WM_CHAR message is sent for keyboard charater input with the
      charater Ord value in the wParam}
    end;

    WM_COMMAND: begin
//    if lParam = Integer(hMessBut) then DoMessage(lParam)
//    else if lParam = Integer(hExitBut) then
//      PostMessage(hAppHandle, WM_CLOSE, 0, 0);
     {when a button is clicked a WM_COMMAND message is sent
     to it's parent's Message Proc with the lParam set to
     the button's Handle}
    end;

    WM_ICONTRAY: begin
      case lParam of
        WM_LBUTTONDBLCLK: begin
          PostMessage(hAppHandle, WM_CLOSE, 0, 0);
        end;
        WM_RBUTTONUP: begin
          PopupTrayIconMenu();
        end;
      end;
    end;
  end;

  Result := DefWindowProc(hWnd,Msg,wParam,lParam);

 {VERY VERY IMPORTANT - to get normal windows default behavior
  you must call DefWindowProc for that message, if you DO NOT want
  normal windows behavior then DO NOT let DefWindowProc be called.
  I have put it at the end of this function, so if you don't want
  DefWindowProc then you just add and "Exit;" in that message
  response above}

end;

////////////////////////////////////////////////////////////////////////////////
BEGIN   // then MAIN Program Begin
 wClass.hInstance := hInstance;
 with wClass do begin
  {there are more wClass parameters which are
  not used here to  keep this simple}
    style :=        0;
    hIcon :=        LoadIcon(hInstance, 'MAINICON');
    lpfnWndProc  := @WndMessageProc;
    hbrBackground:= COLOR_BTNFACE+1;
    {COLOR_BTNFACE is not a brush, but sets it
    to a system brush of that Color}
    lpszClassName:= 'ScriptHostZAAPLite';
    {you may use any class name, but you may want to make it descriptive
    if you register more than one class}
    hCursor :=      LoadCursor(0, IDC_ARROW);
    cbClsExtra :=   0;
    cbWndExtra :=   0;
    lpszMenuName := '';
  end;

  RegisterClass(wClass);

 {the First Window created in a Program for a Registered
  Class will be the apps Main Window or "Form" in Delphi
  terminology, and will be the Main Form for this App.}

  hAppHandle := CreateWindow(
    wClass.lpszClassName,
    'Script Host ZAAP (Lite)',
    WS_OVERLAPPEDWINDOW,
    Integer(CW_USEDEFAULT),
    Integer(CW_USEDEFAULT),
    386,
    250,
    HWND_MESSAGE,
    0,
    hInstance,
    nil
   );

 if hAppHandle = 0 then
 begin
  {I do not usually include a "if hAppHandle = 0 then"
   I put it here to show how to test for success or failure
   of the CreateWindow}
   UnRegisterClass(wClass.lpszClassName, hInstance);
   Exit;
 end;

  with TrayIconData do
  begin
    cbSize := SizeOf(TrayIconData);
    Wnd := hAppHandle;
    uID := 0;
    uFlags := NIF_MESSAGE + NIF_ICON + NIF_TIP;
    uCallbackMessage := WM_ICONTRAY;
    hIcon := LoadIcon(hInstance, 'MAINICON');
    szTip := 'Script Host ZAAP Lite'; // wClass.lpszClassName;
  end;

  Shell_NotifyIcon(NIM_ADD, @TrayIconData);


  ShowWindow(hAppHandle, SW_HIDE);

  {the WS_VISIBLE style was NOT set in the Main window creation
   so you need to call ShowWindow( ) to make it visible.
   This "ShowWindow" with SW_SHOWNORMAL is a Standard way to make your
   program visible, if you use this then your progrm can be started
   by another program as Maximized or Minimized, otherwize those
   options will be ignored}

  UpdateWindow(hAppHandle);

  {the update line above is not needed here because the message loop
   has not started yet, but I have added it to show that you need to
   update to get changes to a window to be visible after the message loop starts}

  while GetMessage(Msg,0,0,0) do
  begin
    {GetMessage will return True until it gets a WM_OUIT message.
     So this program will keep running untill you Post a Quit Message}

    {Translate any WM_KEYDOWN keyboard Msg to a WM_CHAR message}
    TranslateMessage(Msg);

    {this Sends Msg to the address of the
     "Window Procedure" set in the Resistered
     Window's Class for that window}
    DispatchMessage(Msg);
  end;

  {There are 3 calls for CreateWindow for a child window, but
   we do not have to use DestroyWindow, when the
   DefWindowProc(hWnd,Msg,wParam,lParam);
   gets the WM_CLOSE message it will call DestroyWindow( ) for that
   window (hWnd). The system will also destroy all child windows when
   it destroys the parent. There is a complex structure (Record) setup
   in the OS for each window, which records it's height and width, and
   it's Parent and Children, and MANY other things.}
end.

