Программный класс для вывода информации о системном трее

Материал из DRKB


Программный класс для вывода информации о системном трее Показывает все иконки, добавленные в трей, их подсказки и приложение, добавившее иконку. В качестве возможностей класса, добавлена возможность удаления любой иконки. Минус данного класса в том, что заточен исключительно под ХР, на то он и пример :)

////////////////////////////////////////////////////////////////////////////////
//
//  ****************************************************************************
//  * Unit Name : FWSysTrayInfo
//  * Purpose   : Клас для получения информации о системном трее.
//  * Author    : Александр (Rouse_) Багель
//  * Copyright : © Fangorn Wizards Lab 1998 - 2006.
//  * Version   : 1.00
//  * Home Page : http://rouse.drkb.ru
//  ****************************************************************************
//  *
//  * Remark    : Данная версия работает только под ХР,
//  *             если будет необходимость, можно расширить модуль
//  *             для поддержки других систем
//  *

unit FWSysTrayInfo;

interface

uses
  Windows,
  Messages,
  SysUtils,
  Graphics,
  CommCtrl,
  Controls,
  TlHelp32,
  ShellAPI;

type
  DUMMYUNIONNAME = record
    case Integer of
      0: (
        uTimeout: UINT);
      1: (
        uVersion: UINT);
  end;

  // Основная и дополнительная структура для работы с треем
  // ANSI вариант
  _NOTIFYICONDATAA_V1 = record
    cbSize: DWORD;
    Wnd: HWND;
    uID: UINT;
    uFlags: UINT;
    uCallbackMessage: UINT;
    hIcon: HICON;
    szTip: array [0..63] of AnsiChar;
  end;

  P_NOTIFYICONDATAA_V2 = ^_NOTIFYICONDATAA_V2;
  _NOTIFYICONDATAA_V2 = record
    cbSize: DWORD;
    Wnd: HWND;
    uID: UINT;
    uFlags: UINT;
    uCallbackMessage: UINT;
    hIcon: HICON;

    // Расширение структуры для Shell32.dll версии пять
    szTip: array [0..MAXCHAR - 1] of AnsiChar;
    dwState: DWORD;
    dwStateMask: DWORD;
    szInfo: array [0..MAXBYTE - 1] of AnsiChar;
    UNIONNAME: DUMMYUNIONNAME;
    //uTimeout: UINT;
    szInfoTitle:  array [0..63] of AnsiChar;
    dwInfoFlags: DWORD;

    // Расширение структуры для Shell32.dll версии шесть
    //guidItem: DWORD;
  end;

  PNprivIcon = ^TNprivIcon;
  _TNPRIVICON = record
    hWnd: THandle;
    uID: UINT;
    uCallbackMessage: UINT;
    dwState: DWORD;
    uVersion: UINT;
    hIcon: HICON;
  end;
  TNprivIcon = _TNPRIVICON;

  TFWSysIconItem = record
    uID: UINT;
    iImage: Integer;
    szTip: array [0..MAXCHAR - 1] of AnsiChar;
    dwState: DWORD;
    szInfo: array [0..MAXBYTE - 1] of AnsiChar;
    szInfoTitle:  array [0..63] of AnsiChar;
    dwInfoFlags: DWORD;
    hWnd: THandle;
    szAppPath: String;
  end;

  TFWSysIconData = record
    Items: array of TFWSysIconItem;
    ImageList: TImageList;
  end;

  TFWSysTrayInfo = class
  private
    FSysTrayHandle: THandle;
    FIconCount: Integer;
    FIconData: TFWSysIconData;
    function GetImageList: TImageList;
    function GetIconItem(Index: Integer): TFWSysIconItem;
  protected
    function GetAppName(const Wnd: THandle): String;
    procedure DoGetSysTrayHandle; virtual;
    procedure DoGetIconCount; virtual;
    procedure DoGetIconsInfo; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    procedure UpdateInfo;
    procedure DeleteIco(const Index: Integer);
    property IconCount: Integer read FIconCount;
    property IconItem[Index: Integer]: TFWSysIconItem read GetIconItem;
    property ImageList: TImageList read GetImageList;
  end;

implementation

// Выглядит все это вот так:
// Shell_TrayWnd
//    |- Button                     - кнопка старт
//    |- ReBarWindow32
//    |    |- MSTaskSwWClass
//    |    |    +- ToolbarWindow32  - кнопки приложений
//    |    +- ToolbarWindow32       - всякие панели
//    +- TrayNotifyWnd 
//         |- TrayClockWClass       - это там где часики ;)
//         |- SysPager 
//         |    +- ToolbarWindow32  - это наши иконки
//         +- Button                - кнопка свернуть развернуть

const
  Shell_TrayWnd   = 'Shell_TrayWnd';
  TrayNotifyWnd   = 'TrayNotifyWnd';
  SysPager        = 'SysPager';
  ToolbarWindow32 = 'ToolbarWindow32';

{ TFWSysTrayInfo }

constructor TFWSysTrayInfo.Create;
begin
  FIconCount := 0;
  FIconData.ImageList := TImageList.Create(nil);
end;

procedure TFWSysTrayInfo.DeleteIco(const Index: Integer);
var
  IconData: _NOTIFYICONDATAA_V1;
begin
  ZeroMemory(@IconData, SizeOf(_NOTIFYICONDATAA_V1));
  IconData.cbSize := SizeOf(_NOTIFYICONDATAA_V1);
  IconData.uID := FIconData.Items[Index].uID;
  IconData.Wnd := FIconData.Items[Index].hWnd;
  Shell_NotifyIcon(NIM_DELETE, @IconData);
end;

destructor TFWSysTrayInfo.Destroy;
begin
  FIconData.ImageList.Free;
  inherited;
end;

procedure TFWSysTrayInfo.DoGetIconCount;
begin
  FIconCount := SendMessage(FSysTrayHandle, TB_BUTTONCOUNT, 0, 0);
  SetLength(FIconData.Items, FIconCount);
  FIconData.ImageList.Clear;
  ImageList_SetImageCount(FIconData.ImageList.Handle, FIconCount);
end;

function SetDebugPriv: Boolean;
var
  Token: THandle;
  tkp: TTokenPrivileges;
begin
  Result := false;
  if OpenProcessToken(GetCurrentProcess,
    TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, Token) then
  begin
    if LookupPrivilegeValue(nil, PChar('SeDebugPrivilege'),
      tkp.Privileges[0].Luid) then
    begin
      tkp.PrivilegeCount := 1;
      tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
      Result := AdjustTokenPrivileges(Token, False,
        tkp, 0, PTokenPrivileges(nil)^, PDWord(nil)^);
    end;
  end;
end;

procedure TFWSysTrayInfo.DoGetIconsInfo;
var
  dwProcessID, dwBytesWriten: DWORD;
  hProcess, hImageList: THandle;
  pButtonInfo: PTBButtonInfo;
  ButtonInfo: TTBButtonInfo;
  I: Integer;
  szTextBuffer: array [0..MAXCHAR - 1] of Char;
  pszTextBuffer: Pointer;
  NprivIcon: TNprivIcon;
  SHFileInfo: TSHFileInfo;
begin

  // Будем работать с эксплорером, поэтому нужно включить отладочные привилегии
  if not SetDebugPriv then Exit;

  // Получаем ID процесса, которому принадлежит найденное окно
  GetWindowThreadProcessId(FSysTrayHandle, @dwProcessID);
  if dwProcessID = 0 then ExitProcess(GetLastError);

  // Открываем процесс
  hProcess := OpenProcess(PROCESS_ALL_ACCESS or PROCESS_DUP_HANDLE, TRUE, dwProcessID);
  if hProcess <> 0 then
  try
    // Выделяем в нем память под текстовый буффер
    pszTextBuffer := VirtualAllocEx(hProcess, nil, MAX_PATH,
      MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE);
    if pszTextBuffer <> nil then
    try
      // Выделяем в нем память под структуру TTBButtonInfo
      pButtonInfo := VirtualAllocEx(hProcess, nil, SizeOf(TTBButtonInfo),
        MEM_COMMIT or MEM_TOP_DOWN, PAGE_READWRITE);
      if GetLastError = 0 then
      try
        // Заполняем структуру
        ZeroMemory(@ButtonInfo, SizeOf(TTBButtonInfo));
        ButtonInfo.cbSize := SizeOf(TTBButtonInfo);
        ButtonInfo.dwMask := TBIF_IMAGE or TBIF_TEXT or TBIF_STATE or TBIF_STYLE
          or TBIF_LPARAM or TBIF_COMMAND or TBIF_SIZE or TBIF_BYINDEX;
        ButtonInfo.pszText := pszTextBuffer;
        ButtonInfo.cchText := MAXCHAR;

        // Пишем ее в память удаленного процесса
        if not WriteProcessMemory(hProcess, pButtonInfo, @ButtonInfo,
          SizeOf(TTBButtonInfo), dwBytesWriten) then ExitProcess(GetLastError);

        // Получаем информацию со всех элементов
        for I := 0 to FIconCount - 1 do
        begin

          // Отправляем сообщение с указателем на выделенный буффер
          SendMessage(FSysTrayHandle, TB_GETBUTTONINFO,
            I, Integer(pButtonInfo));

          // Читаем информацию о кнопке
          ZeroMemory(@ButtonInfo, SizeOf(TTBButtonInfo));
          ReadProcessMemory(hProcess, pButtonInfo,
            @ButtonInfo, SizeOf(TTBButtonInfo), dwBytesWriten);

          // Читаем подсказку
          ZeroMemory(@szTextBuffer[0], MAXCHAR);
          ReadProcessMemory(hProcess, ButtonInfo.pszText,
            @FIconData.Items[I].szTip[0], ButtonInfo.cchText, dwBytesWriten);

          // Читаем расширенную информацию по кнопке
          ZeroMemory(@NprivIcon, SizeOf(TNprivIcon));
          ReadProcessMemory(hProcess, Pointer(ButtonInfo.lParam),
            @NprivIcon, SizeOf(TNprivIcon), dwBytesWriten);

          // Заполняем структуру данными
          FIconData.Items[I].uID := NprivIcon.uID;
          FIconData.Items[I].dwState := ButtonInfo.fsState;
          FIconData.Items[I].hWnd := NprivIcon.hWnd;
          FIconData.Items[I].iImage := ButtonInfo.iImage;

          // Определяем путь к приложению добавившему иконку
          FIconData.Items[I].szAppPath := GetAppName(NprivIcon.hWnd);

          // Тут хитрый момент, казалось бы - получить иконки
          // можно через ImageList тулбара, получить который можно
          // отправив ему сообщение TB_GETIMAGELIST.
          // Но есть проблема, читаем:
          // http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B811415
          // Поэтому мы пойдем другим путем.
          // Что у нас есть?
          // у нас есть хэндл иконки, которую добавили в трей. (NprivIcon.hIcon)
          // Этот хэндл принадлежит чужому приложению и просто так читать
          // от туда бесполезно.
          // Мы вызовем SHGetFileInfo с флагом SHGFI_SYSICONINDEX,
          // после чего в удаленном приложении создасться ImageList
          // Хэндл этого листа будет у нас на руках и мы можем заменить
          // один из элементов данного листа иконкой, хэндл которой у нас также есть.
          // т.к. оба хэндла принадлежат чужому процессу - замена произойдет
          // безболезненно и мы прочитам результат в свой ImageList
          // Вуаля :)

          ZeroMemory(@SHFileInfo, SizeOf(TSHFileInfo));
          hImageList := SHGetFileInfo(PChar(FIconData.Items[I].szAppPath),
            0, SHFileInfo, SizeOf(TSHFileInfo), SHGFI_SYSICONINDEX or SHGFI_SMALLICON);
          ImageList_ReplaceIcon(hImageList, SHFileInfo.iIcon, NprivIcon.hIcon);
          ImageList_ReplaceIcon(FIconData.ImageList.Handle, ButtonInfo.iImage,
            ImageList_GetIcon(hImageList, SHFileInfo.iIcon, ILD_NORMAL));
        end;
      finally
        VirtualFreeEx(hProcess, pButtonInfo, SizeOf(TTBButtonInfo), MEM_RELEASE);
      end;
    finally
      VirtualFreeEx(hProcess, pszTextBuffer, MAX_PATH, MEM_RELEASE);
    end;
  finally
    CloseHandle(hProcess);
  end;
end;

procedure TFWSysTrayInfo.DoGetSysTrayHandle;
var
  TempHandle: THandle;
begin
  FSysTrayHandle := 0;
  TempHandle := FindWindow(Shell_TrayWnd, nil);
  if TempHandle = 0 then Exit;
  TempHandle := FindWindowEx(TempHandle, 0, TrayNotifyWnd, nil);
  if TempHandle = 0 then Exit;
  TempHandle := FindWindowEx(TempHandle, 0, SysPager, nil);
  if TempHandle = 0 then Exit;
  FSysTrayHandle := FindWindowEx(TempHandle, 0, ToolbarWindow32, nil);
end;

function TFWSysTrayInfo.GetAppName(const Wnd: THandle): String;
var
  hModuleSnap: THandle;
  ModuleEntry: TModuleEntry32;
  CurrentPID: DWORD;
begin
  Result := '';
  GetWindowThreadProcessId(Wnd, CurrentPID);
  hModuleSnap := CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, CurrentPID);
  if hModuleSnap <> INVALID_HANDLE_VALUE then
  try
    ModuleEntry.dwSize := SizeOf(TModuleEntry32);
    if Module32First(hModuleSnap, ModuleEntry) then
      Result := ModuleEntry.szExePath;
  finally
    CloseHandle(hModuleSnap);
  end;
end;

function TFWSysTrayInfo.GetIconItem(Index: Integer): TFWSysIconItem;
begin
  Result := FIconData.Items[Index];
end;

function TFWSysTrayInfo.GetImageList: TImageList;
begin
  Result := FIconData.ImageList;
end;

procedure TFWSysTrayInfo.UpdateInfo;
begin
  DoGetSysTrayHandle;
  DoGetIconCount;
  DoGetIconsInfo;
end;

end.


Author: Александр (Rouse_) Багель
Source: http://rouse.drkb.ru
ID: 01767