Dopisanie elementów do standardowego menu dla pól edycyjnych.

0

Cześc. Wiecie, że rzadko zadaje pytania, ale chciałem się Was poradzić. Czy w ogóle takie coś jak temacie jest możłiwe? Oczywiście w Delphi albo i w innym dowolnym jęzku programowania. Googlowałem, ale nie znalazłem nic konkretnego. Kombinowałem na różne sposoby. Mam wprawdzie już dawno napisany prosty program działąjący na zasadzie Hooka na środkowy przycisk myszki, po którego naciśnięciu pojawia się moje menu tylko na standardowych kontrolkach edycyjnych (o nazwie klasy zawierającej słowo Edit), które pozwala na szybkie zmienienie wielkości liter. Jednak pomyślałem, że fajniej było by dopisać moje pozycje do tego menu od standardowych kontrolek edycyjnych (te które zawiera pozycje Kopiuj, Wklej, Wytnij i inne). Niestety z tego co widziałem chociaż po źródle pluginów do WinAMP'a oraz informacjami z MSDN'a to menu mozna modyfikować można przed jego pokazaniem tylko przy obsłudze komunikatu WM_INITMENUPOPUP, bo wtedy parametr WParam zawiera uchwyt do pokazywanego Menu. Niestety taki kod zarówno dla Edit1.Handle jak i Form1.Handle nie pokazał mi MessageBoxa:

var
  POldEditProc : Pointer;

function NewEditProc(WindowHandle : hWnd; TheMessage : Longint; ParamW : Longint;
  ParamL : Longint) : Longint stdcall;
begin
  if TheMessage = WM_INITMENUPOPUP then
  begin
    MessageBox(Application.Handle, 'WM_INITMENUPOPUP', 'Test', MB_OK);
  end;
  Result := CallWindowProc(POldEditProc, WindowHandle, TheMessage, ParamW, ParamL);
end;

procedure TForm1.FormCreate(Sender : TObject);
begin
  POldEditProc := Pointer(SetWindowLong(Edit1.Handle, GWL_WNDPROC, Longint(@NewEditProc)));
end;

Nie działa też taki kod:

const
  MENU_ID = 3999;
  MenuItemText = 'Nowa pozycja - test' + #9 + 'Ctrl + Q';
var
  HM : HMENU;
  MyMenuItemInfo : MENUITEMINFO;
begin
  HM := GetMenu(Edit1.Handle);
  MessageBox(Application.Handle, PChar(IntToStr(HM)), 'Test', MB_OK);
  MyMenuItemInfo.cbSize := SizeOf(MENUITEMINFO);
  MyMenuItemInfo.fMask := MIIM_ID or MIIM_TYPE or MIIM_DATA;
  MyMenuItemInfo.wID := MENU_ID - 1;
  MyMenuItemInfo.fType := MFT_SEPARATOR;
  InsertMenuItem(HM, 127, False, MyMenuItemInfo);
  MyMenuItemInfo.wID := MENU_ID;
  MyMenuItemInfo.fType := MFT_STRING;
  MyMenuItemInfo.dwTypeData := MenuItemText;
  MyMenuItemInfo.cch := SizeOf(MenuItemText);
  InsertMenuItem(HM, 128, True, MyMenuItemInfo);
end;

Nawet moja ulubiona metoda (w przypadkach gdzie jest pomocna, jak podpięcie się pod funckję obsługi komunikatów innej aplikacji) wstrzyknięcia w obcy proces dllki (dla testów robiłem to na Total Commanderze i polu edycyjnym do podawania poleceń) też nic mi nie dała. Ponieważ również tam WM_INITMENUPOPUP nie zachodziło, a co ciekawe uchwyt tam ustalany w taki sposób jak poniżej zwraca zawsze 1001 i chyba dla innego komunikatu niż WM_INITMENUPOPUP nie będzie on prawidłowy. W zmiennej globalnej EditControlH jest zawarty pobrany uchyt kontrolki edycyjnej na której wcześniej kilknąłem środkowym przyciskiem myszki co przechwytuje globalny Hook. Jak widać zapisuje sobie uchwyt w pamięci jako string korzystając z funkcji CreateFileMapping i MapViewOfFile, a później odczytując. Co realizują moje własne uproszczone funkcje. Przykłady kodów poniżej.

Injekcja przy użyciu modułu: afxcodehook.pas

var
  DestPid : DWORD;
  ResPtr : Pointer;
  HGlobal, HResInfo : THandle;
begin
//  SetForegroundWindow(App_Handle);
//  ShowPopupMenu(ArgDisableCaseMI);
  HResInfo := FindResource(HInstance, Resource_Name, RT_RCDATA);
  if HResInfo > 0 then
  begin
    HGlobal := LoadResource(HInstance, HResInfo);
    ResPtr := PChar(LockResource(HGlobal));
    GetWindowThreadProcessId(EditControlH, DestPid);
    WriteTextAsFileMapping(FileMapping_Name, IntToStr(EditControlH));
    InjectLibrary(OpenProcess(PROCESS_ALL_ACCESS, False, DestPid), ResPtr);
  end;
end;

Fragment zawartości wstrzykiwanej dllki - oczywiście w WinAPI:

//...
function GetMenuStr(MenuHandle : HMENU; ItemID : UINT) : string;
var
  Buf : array[0..255] of Char;
begin
  GetMenuString(MenuHandle, ItemID, Buf, SizeOf(Buf), MF_BYCOMMAND);
  Result := Buf;
end;

function EditFieldCapturedWinFunc(AHWnd : HWND; UMsg : UINT; AWParam : WParam; ALParam : LParam) : integer; stdcall;
const
  MENU_ID = 3999;
  AFileName = 'D:\log.txt';
  MenuItemText = 'Nowa pozycja - test' + #9 + 'Ctrl + Q';
var
  S : string;
  HM : HMENU;
  I : integer;
  MyMenuItemInfo : MENUITEMINFO;
begin
  if UMsg <> WM_PAINT then
  begin
    ReadTextFromFile(AFileName, S);
    if S = '' then
    begin
      S := WinMsgToText(UMsg);
    end
    else
    begin
      S := S + #13#10 + WinMsgToText(UMsg);
    end;
    SaveTextToFile(AFileName, S);
  end;
  if UMsg = WM_CONTEXTMENU then
  begin
    S := '';
    HM := GetMenu(AHWnd);
    for I := 0 to High(WORD) do
    begin
      if S = '' then
      begin
        S := GetMenuStr(HM, I);
      end
      else
      begin
        S := S + #13#10 + GetMenuStr(HM, I);
      end;
    end;
    SaveTextToFile('D:\menu_test.txt', S);
  end;
  if UMsg = WM_CONTEXTMENU then
  begin
    HM := GeTMenu(AHWnd);
    MyMenuItemInfo.cbSize := SizeOf(MENUITEMINFO);
    MyMenuItemInfo.fMask := MIIM_ID or MIIM_TYPE or MIIM_DATA;
    MyMenuItemInfo.wID := MENU_ID - 1;
    MyMenuItemInfo.fType := MFT_SEPARATOR;
    InsertMenuItem(HM, 127, False, MyMenuItemInfo);
    MyMenuItemInfo.wID := MENU_ID;
    MyMenuItemInfo.fType := MFT_STRING;
    MyMenuItemInfo.dwTypeData := MenuItemText;
    MyMenuItemInfo.cch := SizeOf(MenuItemText);
    InsertMenuItem(HM, 128, True, MyMenuItemInfo);
  end;
  Result := CallWindowProc(POrigEditFieldWinFunc, AHWnd, uMsg, AWParam, ALParam);
end;

procedure Apply_New_EditField_Window_Proc;
var
  S : string;
begin
  ReadTextFromFileMapping(FileMapping_Name, S);
  MainEditH := StrToInt(S);
  MessageBox(0, PChar(FormatC('%X', MainEditH)), 'Edit Handle (hex)', MB_ICONINFORMATION + MB_OK);
  POrigEditFieldWinFunc := Pointer(GetWindowLong(MainEditH, GWL_WNDPROC));
  POwnEditFieldWinFunc := @EditFieldCapturedWinFunc;
  SetWindowLong(MainEditH, GWL_WNDPROC, Integer(POwnEditFieldWinFunc));
end;

function HiddenDlgProc(AHWnd : HWND; Msg : UINT; wParam : wParam; lParam : lParam) : BOOL; stdcall;
begin
  Result := False;
  HiddenDialogHandle := AHWnd;
  case Msg of
    WM_INITDIALOG :
      begin
        Apply_New_EditField_Window_Proc;
      end;
    WM_CLOSE :
      begin
        CloseHandle(HMutex);
        PostQuitMessage(0);
      end;
  end;
end;
//...

Plik D:\menu_test.txt jest niestety pusty, także raczej nie pobieram prawidłowego HMENU. Proszę o wszelkie wskazówki. Czy jedynym rozwiązaniem jest do swojego menu tworzonego oryginalnym programem z Hookiem, dodać pozycje do kopiowania, wklejania i wycinania i nimi te zadania obsługiwać. Bo wiadomo że funckja GetSysteMenu pozwala pobrac uchwyt Menu aplikacji i go zmodyfikować, na co są przykłady w sieci także pod Delphi, ale jak z polem edycyjnym i jego menu pod prawym przyciskiem myszki. Proszę o wszelkie porady w tym temacie jeżeli coś wiecie. I przepraszam za rozpisanie się, ale chciałem pokazać oraz opisać metody, które wypróbwałem zanim napisałem tego posta.

0

Odświeżę. Teraz już wiem dlaczego mi zwracało błedny uchwyt. Po prostu to był nie Edit tylko ComboBox. Dla pola eydycjnego z poza ComboBoxa uzyskuje CHYBA właściwe HMenu. Jednak dalej jest problem z niemożnością dodania własnej pozycji do Menu. Taki kod w funkcji obsługi komunikatów dla Edita nie pokazuje MessageBoxa kiedy jest z tą instrukcją warunkową:

    if UMsg = WM_CONTEXTMENU then
    begin
      HM := GetMenu(AHWnd);
      if AppendMenu(HM, MF_STRING, 333, '&Pozycja testowa') then
        MessageBox(0, PChar(IntToStr(HM)), 'Czy działa', MB_OK);
    end;

I teraz głupieje, bo nawet logując do pliku tekstowego komunikaty z okna głownego Total Commandera nie widzę aby otrzymywało ono komunikat: WM_INITMENUPOPUP, a chyba tylko taki musi miec miejsce aby móc dodać cokolwiek do menu. Być może dla standardowych menu on nie zachodzi. Ja się na dzisiaj poddaję, Idę spać. Zajrzę na forum dopiero jutro, także jeżeli macie jakieś pomysły to proszę piszcie.

0

Być może w niczym Ci nie pomogę, ale widziałej już niejednokrotnie dodawanie własnej pozycji do standardowego menu aplikacji - tego, które uruchamia się po kliknięciu LPM lub RPM w ikonę na pasku tytułowym aplikacji, lub po wciśnięciu kombinacji Alt+Space;

Jednym ze znalezionych artykułów jest ten; Dodawana jest w nim pozycja Terminate do standardowego systemowego menu;

Jednak kod nie jest napisany w czystym WinAPI, co pewnie Ciebie nie satysfakcjonuje, ale zajmuje dużo mniej kodu niż u Ciebie :]

Myślę, że skoro łatwo podpiąć się pod to menu, nie problem tak samo zrobić pod inne, też systemowe z kontrolek; Niestety nigdy takiego czegoś nie robiłem w swoich programach, stąd niewiele na ten temat wiem;


Nie rozumiem jednak tego w jakim celu chcesz się podpinać pod już istniejące menu, skoro chyba o wiele szybciej było by napisać swoje; W sieci jest wiele kodów odnośnie "podrabiania" systemowych menu, ale o podpinaniu rzadko się słyszy;

0

@olesio uchwyt, który otrzymujesz też wydał mi się dobry, chociaż nie wiem czy do końca tak jest, ponieważ nie da się nawet pobrać ilości itemów przez** GetMenuItemCount** - zwraca -1, również funkcja IsMenu zwraca FALSE, więc coś jest nie halo. I wydaję mi się, że @furious programming dobrze mówi i zamiast podpinać się pod istniejące już menu, można by je po prostu zastąpić - co raczej nie byłoby problemem, ale nie wiem, może nie zgadza się to z Twoją koncepcją ;)

0
Golden_Mind napisał(a)

ponieważ nie da się nawet pobrać ilości itemów przez GetMenuItemCount - zwraca -1, również funkcja IsMenu zwraca FALSE

To prawda, wygląda na to jakby systemowe menu kontrolek edycyjnych były celowo blokowane/chronione właśnie po to, by nie kombinować i albo korzystać z tych standardowych, albo utworzyć własne; Z racji tej, że stworzenie i oprogramowanie takich podróbek nie trwa zbyt długo i roboty przy nich nie jest za wiele brak modyfikacji aż tak nie boli; Ciekawe tylko jaki jest tego cel? Dla mnie blokowanie tegoż menu sensu większego nie ma;

Ale to, że można to zrobić prościej przez obejście nie zawsze satysfakcjonuje; @olesio pewnie ma na celu udowodnienie (sobie?), że takie coś jest możliwe; Szkoda tylko, że WinAPI tego nie oferuje; Na pewno istnieje jakieś rozwiązanie, ale jest według mnie znacznie trudniejsze do wykrycia niż użycie podstawowych operacji do tego przystosowanych z czystego WinAPI;

0

@Furious Programming i @Golden_Mind dziękuję za odpowiedzi. Cóż jednak sobie daruje temat. Artykuł, który pokazał @furious programming widziałem i tam modyfikuje się menu systemowe, a nie konkretniej kontrolki. Oczywiście mogę przerobić hook tak aby pokazywał własne menu na kontrolkach eydycjnych po prawo kliku, ale tak głupio trochę dodać po angielsku opisane pozycje Cut, Copy, Paste zamiast tych po polsku czy w innym języku systemu, chociaż mój program jest i tak po angielsku, więc może to nie aż taki problem. Chociaż poza tymi pozycjami do operacji na tesktcie są na przykład pod Windows 7 inne pozycje, których obsługi nie bardzo umiałbym dokonać samodzielnie. Anyway, czyli jest tak jak podejrzewałem, otwieranie własnego menu to jedyne rozwiązanie. Może Microsoft projektując WIndows wybiegł myślami tak daleko, że ktoś dodając albo podmieniając na przykład menu jakiejś kontrolki może coś kombinować ze szkodliwym kodem i dlatego takiej możłiwości nie ma dla bezpieczeństwa. Nic, porzucam póki co to kombinowanie, ale szkoda że się raczej nie da tego zrobić ;/ Dla swoich celów mogę mieć jak do tej pory Hook na środkowy przycisk myszki i po kliknięciu nim pokazywać własne menu.

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