Pobieranie tekstu z ikony w trayu w WinAPI

0

witam! czy jest możliwość za pomocą winapi (lub innym sposobem) pobrać tekst który pojawia się kiedy najedziemy na jakąś ikonkę w trayu (ten w dymku)? Jeśli tak to jaka funkcja? z góry dzięki!

0

program rzeczywiście wyciąga dokładnie tą informacje którą szukam, niestety muszę chyba odpuścić ponieważ z moją wiedzą nie jestem w wyłuskać prawidłowego kodu, dzięki kAzek za fatyge

1

Z tego co widze, najwązniejszy kod dla ciebie jest w pliku ShellTrayInfoView.cpp i zaczyna się od tekstu void CShellTrayInfoView::ListTrayIcons. Sam próbowałem znalezione w google kody w Delphi z użyciem komunikatu TB_GETBUTTON przerobić i uzupełnić aby odczytywały też tak zwany ToolTip, ale nie bardzo mi to wyszło.

0

Wersja dla Delphi (ale odpowiedzialne za pobranie info funkcje to WinApi więc można sobie przetłumaczyć na inny język) pobierania info o ikonach w trayu może komuś się przyda:
Na formie ListView, ImageList i Button
ListView ma 3 kolumny i przypisany jako SmallImages ImageList

//do uses CommCtrl, PsApi

//zwraca sciezkę i nazwe proceu parametr to PID
function GetFilenameFromPid(PID: Cardinal): string;
var
  hProcess: Cardinal;
  buff: array[0..MAX_PATH - 1] of Char;
begin
  result:= '';
  hProcess:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, PID);
  if (hProcess > 0) then
  begin
    if GetModuleFileNameEx(hProcess, 0, buff, MAX_PATH) > 0 then
      result:= buff;
    CloseHandle(hProcess);
  end;
end;

//zwraca uchwyt toolbara traya
function FindTrayToolbarWindow: Cardinal;
const
  WND_CLASS_ARRAY: array [0..3] of string =
    ('Shell_TrayWnd', 'TrayNotifyWnd', 'SysPager', 'ToolbarWindow32');
var
  i: Integer;
begin
  i:= Low(WND_CLASS_ARRAY);
  result:= FindWindow(PAnsiChar(WND_CLASS_ARRAY[i]), nil);
  Inc(i);
  while ((result > 0) and (i <= High(WND_CLASS_ARRAY))) do
  begin
    result:= FindWindowEx(result, 0, PAnsiChar(WND_CLASS_ARRAY[i]), nil);
    Inc(i);
  end;
end;

procedure TForm1.btnLoadTrayIconsInfoClick(Sender: TObject);

type
  _EXTRADATA = packed record
    hWnd: THandle;
    uID: UINT;
    uCallbackMessage: UINT;
    Reserved: array [0..1] of DWORD;
    hIcon: HICON;
  end;

var
  pTrayBtnData: Pointer;
  hTray, hProcessExplorer: Cardinal;
  dwExplorerProcessID, dwBytesRead, dwTrayButtonCount: Cardinal;
  ButtonData: _TBBUTTON;
  ExtraData: _EXTRADATA;
  ToolTip: array [0..1024] of WideChar;
  pIconInfo: _ICONINFO;
  i: Integer;

  dwInfoProcessID: Cardinal;
  sInfoProcessName: string;
  sInfoToolTip: string;
  hInfoIcon: Cardinal;

  li: TListItem;
  ico: TIcon;
begin
  ImageList1.Clear;
  ListView1.Clear;

  hTray:= FindTrayToolbarWindow;
  if hTray = 0 then exit;
  if (GetWindowThreadProcessId(hTray, dwExplorerProcessID) = 0) then exit;
  hProcessExplorer:= OpenProcess(PROCESS_ALL_ACCESS, False, dwExplorerProcessID);
  if (hProcessExplorer = 0) then exit;
  pTrayBtnData:= VirtualAllocEx(hProcessExplorer, nil, SizeOf(_TBBUTTON),
    MEM_COMMIT, PAGE_READWRITE);
  if (Assigned(pTrayBtnData)) then
  begin
    dwTrayButtonCount:= SendMessage(hTray, TB_BUTTONCOUNT, 0, 0);
    for i:= 0 to dwTrayButtonCount - 1 do
    begin
      SendMessage(hTray, TB_GETBUTTON, i, Longint(pTrayBtnData));
      if ReadProcessMemory(hProcessExplorer, pTrayBtnData, @ButtonData,
           SizeOf(_TBBUTTON), dwBytesRead) and (dwBytesRead = SizeOf(_TBBUTTON)) then
      begin
        sInfoProcessName:= '';
        hInfoIcon:= 0;
        if ReadProcessMemory(hProcessExplorer, Pointer(ButtonData.dwData),
             @ExtraData, SizeOf(_EXTRADATA), dwBytesRead) and
             (dwBytesRead = SizeOf(_EXTRADATA)) then
        begin
          GetWindowThreadProcessId(ExtraData.hWnd, dwInfoProcessID);
          sInfoProcessName:= GetFilenameFromPid(dwInfoProcessID);
          hInfoIcon:= ExtraData.hIcon;
        end;
        sInfoToolTip:= '';
        if ReadProcessMemory(hProcessExplorer, Pointer(ButtonData.iString),
             @ToolTip, 1024, dwBytesRead) and (dwBytesRead = 1024) then
        sInfoToolTip:= WideCharToString(ToolTip);

        li:= ListView1.Items.Add;
        li.SubItems.Add(sInfoToolTip);
        li.SubItems.Add(sInfoProcessName);
        li.ImageIndex:= -1;
        if GetIconInfo(hInfoIcon, pIconInfo) then
        begin
          ico:= TIcon.Create;
          try
          ico.Handle:= hInfoIcon;
          li.ImageIndex:= ImageList1.AddIcon(ico);
          finally
          ico.Free;
          end;
        end;

      end;
    end;
    VirtualFreeEx(hProcessExplorer, pTrayBtnData, 0, MEM_RELEASE);
  end;
  CloseHandle(hProcessExplorer);
end;
0

@kAzek: rozumiem, że kod działa u Ciebie prawidłowo? Bo u mnie efekt jego działania jest widoczny na obrazie poniżej. Za pewne "winny" jest tutaj 64 bitowy system. Jednak kody znalezione na google dla wersji 64 bitowej, wprawdzie na podobnej zasadzie, ale tylko dla odświeżenia obszaru Tray, też nie pomogły mi sklecić nic samodzielnie. Po prostu if po hInfoIcon := 0; nie wykonuje się, a poza tym nawet jeżeli by z niego zrezygnować to ExtraData.hWnd pokazuje zawsze ten sam uchwyt. Masz może pomysł jak zmusić do prawidłowego działania Twój kod na systemie Windows 7 Ultimate 64 bit? Bo moze trzeba było by jakoś "scalić" kod z http://www.delmadang.com/community/bbs_print.asp?bbsNo=3&bbsCat=0&indx=428067 który działa ok jeśli chodzi o odświeżanie i przetumaczyć kod z http://www.haogongju.net/art/1282689 na Delphi, który uwzględnia bitowośc systemu.
kazek_code_test.jpg

0

@kAzek: próbowałem tłumaczyć kod z: http://www.haogongju.net/art/1282689 - stamtąd można zobaczyć różnicę w ustawianiu offsetu. tyle co potrafiłem to przetłumaczyłem na Delphi, ale chyba nie do konca prawidłowo. Próbując zapisać albo wyświetlić zmienną buff otrzymuje jakieś znaczki ASCII. Może spojrzysz - kod i exek w załączniku. Natomiast, tak jak pisałem kod z tej strony jeżeli chodzi o odświeżanie działa ok na 64 bitach, tylko nie umiem go zaadaptować do odczytania informacji takich jak ToolTip oraz ewentualnie ikonka danego procesu: http://www.delmadang.com/community/bbs_print.asp?bbsNo=3&bbsCat=0&indx=428067 - jak uda się Tobie coś wykombinować i bedzie to uniwersalny kod dla 32 bitów oraz 64 bitów to proszę daj przykład. Do sprawdzania bitowości systemu najprościej będzie chyba wykorzystać poniższą funkcję, która kompiluje się oczywiście również pod starszymi wersjami Delphi, jak siódemka.

function IsWow64 : boolean;
type
  TIsWow64Process = function(hProcess : THANDLE; Wow64Process : PBOOL) : BOOL; stdcall;
var
  IsWow64Process : TIsWow64Process;
begin
  IsWow64Process := GetProcAddress(GetModuleHandle('kernel32'), 'IsWow64Process');
  if Assigned(IsWow64Process) then
  begin
    IsWow64Process(GetCurrentProcess, @Result);
  end;
end;
0

@olesio sprawdź nową wersję kodu jest zgodna z nowymi wersjami Delphi, teoretycznie powinna działać pod 64bit ale nie sprawdziłem bo nie mam jak. Jeżeli nadal nie działa to myślę że pod 64 _EXTRADATA też trzeba inaczej zdefiniować :/ Jak coś to napisz co się dzieje tym razem.

//do uses CommCtrl, PsApi

//zwraca sciezkę i nazwe proceu parametr to PID
function GetFilenameFromPid(PID: Cardinal): string;
var
  hProcess: Cardinal;
  buff: array[0..MAX_PATH - 1] of Char;
begin
  result:= '';
  hProcess:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, PID);
  if (hProcess > 0) then
  begin
    if GetModuleFileNameEx(hProcess, 0, buff, MAX_PATH) > 0 then
      result:= buff;
    CloseHandle(hProcess);
  end;
end;

//zwraca uchwyt toolbara traya
function FindTrayToolbarWindow: Cardinal;
const
  {$IFDEF UNICODE}
  WND_CLASS_ARRAY: array [0..3] of PWideChar =
      ('Shell_TrayWnd', 'TrayNotifyWnd', 'SysPager', 'ToolbarWindow32');
  {$ELSE}
  WND_CLASS_ARRAY: array [0..3] of PAnsiChar =
      ('Shell_TrayWnd', 'TrayNotifyWnd', 'SysPager', 'ToolbarWindow32');
  {$ENDIF}
var
  i: Integer;
begin
  i:= Low(WND_CLASS_ARRAY);
  result:= FindWindow(WND_CLASS_ARRAY[i], nil);
  Inc(i);
  while ((result > 0) and (i <= High(WND_CLASS_ARRAY))) do
  begin
    result:= FindWindowEx(result, 0, WND_CLASS_ARRAY[i], nil);
    Inc(i);
  end;
end;

function IsWow64: Boolean;
type  //tak to musi byc bo inaczej sie wyklada w nowych Delphi
  TIsWow64Process = function(hProcess : THANDLE; var Wow64Process: BOOL): BOOL; stdcall;
var
  IsWow64: BOOL;
  IsWow64Process: TIsWow64Process;
begin
  result:= False;
  @IsWow64Process := GetProcAddress(GetModuleHandle('kernel32'), 'IsWow64Process');
  if Assigned(IsWow64Process) then
  begin
    IsWow64Process(GetCurrentProcess, IsWow64);
    result:= IsWow64;
  end;
end;

procedure TForm1.btnLoadTrayIconsInfoClick(Sender: TObject);
type
  {$IFNDEF _TBBUTTON}
  _TBBUTTON = packed record
    iBitmap: Integer;
    idCommand: Integer;
    fsState: Byte;
    fsStyle: Byte;
    bReserved: array[1..2] of Byte;
    dwData: Longint;
    iString: Integer;
  end;
  {$ENDIF}

  {$IFNDEF _TBBUTTON64}
  _TBBUTTON64 = packed record
    iBitmap: Integer;
    idCommand: Integer;
    fsState: Byte;
    fsStyle: Byte;
    iReserved: Integer;
    bReserved2: array[1..2] of Byte;
    dwData: Longint;
    iString: Integer;
  end;
  {$ENDIF}

  _EXTRADATA = packed record
    hWnd: THandle;
    uID: UINT;
    uCallbackMessage: UINT;
    Reserved: array [0..1] of DWORD;
    hIcon: HICON;
  end;

const
  {$IFNDEF TB_GETBUTTON}
  TB_GETBUTTON = WM_USER + 23;
  {$ENDIF}
  {$IFNDEF TB_BUTTONCOUNT}
  TB_BUTTONCOUNT = WM_USER + 24;
  {$ENDIF}
var
  pTrayBtnData: Pointer;
  dwTrayBtnDataSzie: Cardinal;
  pButtonData: Pointer;

  hTray, hProcessExplorer: Cardinal;
  dwExplorerProcessID, dwTrayButtonCount: Cardinal;
  {nie wiem dokladnie od jakiej wersji musi byc NativeUInt zakladam w ciemno że od XE jak nie chce sie kompilowac to trzeba zmienic}
  {$IF CompilerVersion >= 22}
  dwBytesRead: NativeUInt;
  {$ELSE}
  dwBytesRead: Cardinal;
  {$IFEND}
  ExtraData: _EXTRADATA;
  ToolTip: array [0..1024] of WideChar;
  pIconInfo: _ICONINFO;
  i: Integer;

  dwInfoProcessID: Cardinal;
  sInfoProcessName: string;
  sInfoToolTip: string;
  hInfoIcon: Cardinal;

  li: TListItem;
  ico: TIcon;

  nDataOffset: Integer;
  nStrOffset: Integer;

  bIs64bit: Boolean;
begin
  {$IFNDEF WIN64} //czy 64bit wersja aplikacji
  bIs64bit:= IsWow64;
  {$ELSE} //no bez jaj pod Mac OS to i tak nie pojdzie wiec nie ma co sie p...c
  bIs64bit:= True;
  {$ENDIF}

  if bIs64bit then
    dwTrayBtnDataSzie:= SizeOf(_TBBUTTON64)
  else
    dwTrayBtnDataSzie:= SizeOf(_TBBUTTON);

  ImageList1.Clear;
  ListView1.Clear;


  hTray:= FindTrayToolbarWindow;
  if hTray = 0 then exit;
  if (GetWindowThreadProcessId(hTray, dwExplorerProcessID) = 0) then exit;
  hProcessExplorer:= OpenProcess(PROCESS_ALL_ACCESS, False, dwExplorerProcessID);
  if (hProcessExplorer = 0) then exit;
  pTrayBtnData:= VirtualAllocEx(hProcessExplorer, nil, dwTrayBtnDataSzie,
    MEM_COMMIT, PAGE_READWRITE);
  if (Assigned(pTrayBtnData)) then
  begin
    pButtonData:= AllocMem(dwTrayBtnDataSzie);
    dwTrayButtonCount:= SendMessage(hTray, TB_BUTTONCOUNT, 0, 0);
    for i:= 0 to dwTrayButtonCount - 1 do
    begin
      SendMessage(hTray, TB_GETBUTTON, i, Longint(pTrayBtnData));
      if ReadProcessMemory(hProcessExplorer, pTrayBtnData, pButtonData,
           dwTrayBtnDataSzie, dwBytesRead) and (dwBytesRead = dwTrayBtnDataSzie) then
      begin
        if bIs64bit then
        begin
          nDataOffset:= _TBBUTTON64(pButtonData^).dwData;
          nStrOffset:= _TBBUTTON64(pButtonData^).iString;
        end
        else
        begin
          nDataOffset:= _TBBUTTON(pButtonData^).dwData;
          nStrOffset:= _TBBUTTON(pButtonData^).iString;
        end;

        dwInfoProcessID:= 0;
        sInfoProcessName:= '';
        hInfoIcon:= 0;
        if ReadProcessMemory(hProcessExplorer, Pointer(nDataOffset),
             @ExtraData, SizeOf(_EXTRADATA), dwBytesRead) and
             (dwBytesRead = SizeOf(_EXTRADATA)) then
        begin
          GetWindowThreadProcessId(ExtraData.hWnd, dwInfoProcessID);
          sInfoProcessName:= GetFilenameFromPid(dwInfoProcessID);
          hInfoIcon:= ExtraData.hIcon;
        end;
        sInfoToolTip:= '';
        if ReadProcessMemory(hProcessExplorer, Pointer(nStrOffset),
            @ToolTip, 1024, dwBytesRead) and (dwBytesRead = 1024) then
          sInfoToolTip:= WideCharToString(ToolTip);

        li:= ListView1.Items.Add;
        li.SubItems.Add(sInfoToolTip);
        li.SubItems.Add(sInfoProcessName);
        li.ImageIndex:= -1;
        if GetIconInfo(hInfoIcon, pIconInfo) then
        begin
          ico:= TIcon.Create;
          try
          ico.Handle:= hInfoIcon;
          li.ImageIndex:= ImageList1.AddIcon(ico);
          finally
          ico.Free;
          end;
        end;

      end;
    end;
    FreeMem(pButtonData);
    VirtualFreeEx(hProcessExplorer, pTrayBtnData, 0, MEM_RELEASE);
  end;
  CloseHandle(hProcessExplorer);
end;
0

Dziękuję za poświęcenie czasu @kAzek. Ale niestety, w przypadku dwóch procesów pokazują się tylko ich prawidłowe ścieżki (akurat mialem uruchomiony WinAPI i mój program, który globalnym hookiem wykrywa i przebindowuje działanie jednego z klawiszy na klawiaturze). Nadal jednek brak ikonek oraz ToolTipów. A @Azarien: jaką funkcję do tego proponujesz i czy masz może pomysł jak pod Delphi rozwiązać problem poruszony w wątku dla 64 `itowego systemu. Może jednak kod z http://www.haogongju.net/art/1282689 dobrze przetłumaczony na Delphi by zadziałał jak trzeba?

0

@olesio nie ma sensu tłumaczyć tamtego kodu bo to na jedno wychodzi :/
Na razie błąd to deklaracja _TBBUTTON64 zaraz wyjaśnię o co chodzi:
Według kodu ze strony którą podałeś tam jest:

int nDataOffset = sizeof(TBBUTTON) - sizeof(INT_PTR) - sizeof(DWORD_PTR);  //12
  int nStrOffset = 18;
  if ( IsWow64() ){
        //to sie zgadza  bo dochodza 4 bajty przed _TBBUTTON64.dwData
        nDataOffset+=4;
        //a to z dupy wziete bo size of Integer = 4, sizeof longint = 4  sizeof UInt64 = 8 to
        //do cholery jakiego typu jest  _TBBUTTON64.nDataOffset jak chcą aby zajmowal 6 bajtow
        nStrOffset+=6;
  }
 Więc można spróbować deklarować tak:

   {$IFNDEF _TBBUTTON64}
  _TBBUTTON64 = packed record
    iBitmap: Integer;
    idCommand: Integer;
    fsState: Byte;
    fsStyle: Byte;
    bReserved: array[1..6] of Byte;
    dwData: Longint;
    bReserved: array[1..2] of Byte; //tu dodamy te 2 'zgubione' bajty
    iString: Integer;
  end;
  {$ENDIF}

i wtedy teoretycznie powinno działać.

Ale na msdn znalazłem:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680553%28v=vs.85%29.aspx
i tam:

typedef struct
{
  INT       iBitmap;
  INT       idCommand;
  BYTE      fsState;
  BYTE      fsStyle;
  BYTE      bReserved[6];
  UINT64    dwData;
  UINT64    iString;
} TBBUTTON64;

a więc TBBUTTON64.dwData i TBBUTTON64.iString są typu UINT64 a ten zajmuje 8 bajtów więc jestem głupi można spróbować zadeklarować tak:

  {$IFNDEF _TBBUTTON64}
  _TBBUTTON64 = packed record
    iBitmap: Integer;
    idCommand: Integer;
    fsState: Byte;
    fsStyle: Byte;
    bReserved: array[1..6] of Byte;
    dwData: UINT64;
    iString: UINT64;
  end;
  {$ENDIF}

tyle że w kodzie jest rzutowanie na Pointer i znowu do d**y ale spróbuj obie wersje.

PS: Brak ikony moze być z kolei błędem w deklaracji _EXTRADATA (też moze sie różnić na 64 bit) ale to na razie niw ważne trzeba ToolTip obczaić pierwsze a nie w 2 miejscach grzebać jednocześnie

0

Chyba jednak w pierwszej wersji (w tej co podałem cały kod nowej wersij) deklaracja _TBBUTTON64 jest ok tylko spróbuj zamiast tamtego _EXTRADATA wszędzie (do testu na razie na sztywno) użyć _EXTRADATA64 jak i stopniowo zwiększaj wielkosć tablicy Reserved2 (myślę że nie więcej niż do 16) jak będzie będzie ok to powinna być ikonka:

_EXTRADATA64 = packed record
    hWnd: THandle;
    uID: UINT;
    uCallbackMessage: UINT;
    Reserved: array [1..2] of DWORD;
    Reserved2: array[1..n] of Byte;
    hIcon: HICON;
  end;

Co do ToolTip to sprawa taka że nie wiem ReadProcessMemory odczytuje?
Moze tam nie ma być WideCharToString albo ToolTip: array [0..1024] of WideChar; ma być Char czy coś najlepiej w Watches sobie podglądnij co masz w zmiennej ToolTip po ReadProcessMemory.

0

Przy założeniu, że n = 2 i poprawieniu typów, efekt jest taki sam. U mnie ToolTip pokazuje w MessageBoxie same znaki zapytania, a po konwersji wykonanej jak u Ciebie - jest to pusty string. Trudno, ja sam nie mam ochoty dociekać co i jak, a Ty bez 64 bitowego systemu też nie dojdziesz. Może ktoś inny się jeszcze właczy w ten wątek i wymyśli uniwersalny kod, który zadziała też pod 64 bitowym systemem. Niemniej jednak - jeszcze raz, dziękuje Tobie za czas jaki poświęciłeś aby to w ogóle działąlo chociaż na 32 bitowym systemie kompilując pod Delphi.

0

Zawaliliście cały wątek niedziałającymi kodami w Delphi nie pytając nawet autora w jakim języku pisze. A najprawdopodobniej chodzi o C#, ew. C++.

0

No to już autor powinien sprecyzowac. Nie nasz wina. A w WinAPI oczywiście równie dobrze można pisać i pod Delphi. Natomiast podałem też link do kodu w C++ gdzie ze screenshotów wynika, że działa. I jest sprawdzanie czy mamy do czynienia z uruchomieniem pod 64 bitowym systemem oraz odpowiednią reakcją na ten fakt.

0

@Azarien: jaką funkcję do tego proponujesz i czy masz może pomysł jak pod Delphi rozwiązać problem poruszony w wątku dla 64 `itowego systemu.

Funkcja jest dobra (IsWow64Process), tylko jej użycie nie.

Nie mam pod ręką gotowego przykładu, ale należy użyć jej tak:
• jeśli nasz program jest 64-bitowy (a to możemy sprawdzić jakimś ifdefem, albo sizeof(pointer)), to jest oczywiste, że system jest 64-bitowy. inaczej program by się przecież nie uruchomił...
• jeśli nasz program jest 32-bitowy, to system może być 32-, albo 64-bitowy. i teraz sprawdzamy tak:
· jeśli funkcji IsWow64Process nie da się załadować, to system jest na pewno 32-bitowy.
· jeśli funkcja zwraca true, to system jest 64-bitowy,
· jeśli funkcja zwraca false, to system jest 32-bitowy.

IsWow64Process() zwraca true tylko dla programu 32-bitowego uruchomionego pod systemem 64-bitowym. czyli w programach 64-bitowych zwraca false.

0

Przepraszam za pierwszy post byłem pewny że uwzględniłem preferowana technologię, jest to c# w ostateczności c++. Niestety raczej muszę jednak zrezygnować z tego projektu ;)

0

@Markness: jak uważasz, ale może nie poddawaj się. Kody, które podal @kAzek działają na systemie 32 bitowym. Tak on twierdzi, a ja nie mam podstaw by mu nie wierzyć. Poza tym kod w C++ z pod jednego, z adresów. które podałem również powinien działać, jeżeli wierzyć autorowi dołaczonych do wpisu na stronie zrzutów ekranowych. W tym drugim przypadku uniknął byś konieczności tlumaczenia tego z Delphi.

1 użytkowników online, w tym zalogowanych: 0, gości: 1