Находить и использовать текущий активный чат в клиенте Skype через WinAPI & Delphi? - PullRequest
2 голосов
/ 19 апреля 2011

В Delphi, используя Skype API, я могу довольно просто отправить сообщение контакту.Однако то, что я пытаюсь сделать, это ввести сообщение в окно чата текущего сосредоточенного контакта, не отправляя сообщение.

Используя Winspector, я обнаружил, что имя класса чата - TChatRichEdit, которыйпомещается в TChatEntryControl, который помещается в TConversationForm, и, наконец, помещается в tSkMainForm.(Очевидно, что клиент Skype написан на Delphi;))

Используя Win API, как найти правильный tSkMainForm> TConversationForm> TChatEntryControl> TChatRichEdit , а затем ввести сообщение вэто?

Каков наилучший способ для этого?

Кроме того, TConversationForm также содержит имя контакта, так что я думаю, что это немного облегчает?

РЕДАКТИРОВАТЬ: Вот скриншот Windspector Spy, показывающий TChatRichEdit:

Winspector Spy

Вот мой текущий код:

function GetConversationWindow(Wnd: HWnd; P: LParam): Bool; stdcall;
var
  Param: PGetConversationParam;
  ProcID: DWord;
  // WndClass docs say maximum class-name length is 256.
  ClassName: array[0..256] of Char;
  WindowTitle: array[0..256] of Char;
begin
  Result := True; // assume it doesn't match; keep searching
  Param := PGetConversationParam(P);

  GetWindowThreadProcessID(Wnd, @ProcID);
  if ProcID <> Param.ProcID then
    Exit;

  if GetClassName(Wnd, ClassName, Length(ClassName)) = 0 then
    Exit;
  if StrComp(ClassName, 'TConversationForm') <> 0 then
    Exit;

  if SendMessage(Wnd, wm_GetText, Length(WindowTitle), LParam(@WindowTitle[0])) = 0 then
    Exit;
  if Param.ContactName = WindowTitle then begin
    Param.Result := Wnd;
    Result := False;
  end;
end;



procedure TForm1.Button1Click(Sender: TObject);
var
  Param: TGetConversationParam;
  RichEditWnd, ControlWnd : HWND;
  ParentWnd : HWND;
begin
  //Param.ProcID := GetSkypeProcessID;
  Param.ContactName := 'xSky Admin';
  ParentWnd := FindWindowEx(0,0,'tSkMainForm',nil);

  if EnumChildWindows(ParentWnd,@GetConversationWindow, LParam(@Param)) then
    Abort; // Didn't find it.

  // Param.Result holds the conversation window's handle. Now walk its children.
  ControlWnd := FindWindowEx(Param.Result, 0, 'TChatEntryControl', nil);
  if ControlWnd = 0 then
    Abort; // Conversation doesn't have an entry control

  RichEditWnd := FindWindowEx(ControlWnd, 0, 'TChatRichEdit', nil);
  if RichEditWnd = 0 then
    Abort;

  ShowMessage('Got it!');
end;

Я никогда не достигаюthe ShowMessage.

Вот скриншот моей IDE в режиме отладки:

IDE in Debug Mode

Я добавил точку останова на линии прерывания.

Есть идеи?

Ответы [ 2 ]

4 голосов
/ 19 апреля 2011

Примерно так:

var
  aHandle   : cardinal;
begin
   aHandle := FindWindow(PWideChar('TChatRichEdit'), nil);
   result  := aHandle <> 0;
   if result then
      PostMessage(aHandle, WM_...); 

Тогда у вас есть дескриптор этого окна.Вы можете использовать WM_SETTEXT или что-то для ввода текста.Но Skype использует WM_COPYDATA для связи с другими программами, и наоборот.Вы должны искать StackOverflow для этого.

1 голос
/ 19 апреля 2011

Я думаю, TConversationForm - это окно верхнего уровня.Используйте EnumWindows, чтобы найти это.(Пока не беспокойтесь о FindWindow; он всегда возвращает первое найденное окно, поэтому, если активно несколько разговоров, у вас нет контроля над ним.)

type
  PGetConversationParam = ^TGetConversationParam;
  TGetConversationParam = record
    ProcID: DWord;
    ContactName: string;
    Result: HWnd;
  end;

function GetConversationWindow(Wnd: HWnd; P: LParam): Bool; stdcall;
var
  Param: PGetConversationParam;
  ProcID: DWord;
  // WndClass docs say maximum class-name length is 256.
  ClassName: array[0..256] of Char;
  WindowTitle: array[0..256] of Char;
begin
  Result := True; // assume it doesn't match; keep searching
  Param := PGetConversationParam(P);

  GetWindowThreadProcessID(Wnd, @ProcID);
  if ProcID <> Param.ProcID then
    Exit;

  if GetClassName(Wnd, ClassName, Length(ClassName)) = 0 then
    Exit;
  if StrComp(ClassName, 'TConversationForm') <> 0 then
    Exit;

  if SendMessage(Wnd, wm_GetText, Length(WindowTitle), LParam(@WindowTitle[0])) = 0 then
    Exit;
  if Param.ContactName = WindowTitle then begin
    Param.Result := Wnd;
    Result := False;
  end;
end;

ЭтоФункция проверяет несколько вещей, чтобы убедиться, что смотрит в нужное окно.Он проверяет, что окно принадлежит процессу Skype, что у него есть ожидаемый класс окна, и что его заголовок является именем целевого контакта.Если Skype добавляет дополнительный текст в заголовок окна, вам нужно убедиться, что он выглядит «достаточно близко».Не просто позвоните по номеру Pos, чтобы узнать, появляется ли имя контакта где-то в заголовке;если у какого-либо контакта есть имя, которое является подстрокой заголовка окна беседы, вы можете случайно найти совпадение, если не должны.

Идентификатор процесса не является строго обязательным, поэтомуВы можете опустить эту часть, если не знаете идентификатор процесса.

Функция EnumWindows будет вызывать вышеуказанную функцию один раз для каждого окна верхнего уровня.Если окно является тем, которое вы ищете, GetConversationWindow возвращает False , чтобы сказать: «Я нашел то, что хочу, поэтому, пожалуйста, перестаньте спрашивать больше».В противном случае возвращается Верно : «Это был не тот, поэтому, пожалуйста, дайте мне другой».Если GetConversationWindow когда-либо вернет False , то EnumWindows также вернет False , а поле Param.Result будет содержать дескриптор искомого окна.Если у вас есть это, используйте FindWindowEx для навигации по остальной иерархии окон:

var
  Param: TGetConversationParam;
begin
  Param.ProcID := GetSkypeProcessID;
  Param.ContactName := GetSkypeContactName;
  if EnumWindows(@GetConversationWindow, LParam(@Param)) then
    Abort; // Didn't find it.

  // Param.Result holds the conversation window's handle. Now walk its children.
  ControlWnd := FindWindowEx(Param.Result, 0, 'TChatEntryControl', nil);
  if ControlWnd = 0 then
    Abort; // Conversation doesn't have an entry control

  RichEditWnd := FindWindowEx(ControlWnd, 0, 'TChatRichEdit', nil);
  if RichEditWnd = 0 then
    Abort;

  // Voila!
end;
...