Как в диалоговом окне "Безопасное извлечение оборудования" Windows появляется "передняя любовь"?

Раймонд рассказал о, как программы могут получить/украсть "переднюю любовь", используя RegisterHotkey, который при вызове передаст переднюю часть ваше приложение.

Попытки сделать это вручную терпят неудачу (например, используя SetForegroundWindow, SwitchToWindow и т.д.), потому что приложения не могут украсть фокус у пользователя (так, чтобы нажатия клавиш не попадали в неправильное место).

Беда в том, что сегодня я заметил что-то странное:

  • Я пытаюсь безопасно удалить внешний диск.

  • Существует пауза в 7 секунд.

  • Во время паузы я активно печатаю внутри окна.

  • Внезапно окно с сообщениями перечеркивает переднюю часть моего приложения, и вместо этого я печатаю текст в поле сообщения.

Понятно, что это не использует механизм горячих клавиш - и все же Windows смогла украсть фокус из моего приложения.

Я действительно сомневаюсь, что есть что-то вроде "бэкдора", используемого только для этой конкретной цели (хотя, пожалуйста, поправьте меня, если я ошибаюсь), поэтому, предполагая, что это не так, должен быть способ сделайте это правильно, не используя механизм горячих клавиш.

Итак, вопрос в том, как это достигается?

Примечание:

Ганс отметил, что "бэкдор" - AttachInputThread, но я не совсем уверен, что здесь происходит, тем более что Раймонд говорит, что метод может вызвать зависания. Идеи?

Ответ 1

Я экспериментировал, и из того, что я вижу, это происходит тогда и только тогда, когда новое окно принадлежит Windows Explorer. Например, некоторые панели управления реализованы в проводнике Explorer или как плагины Explorer. Я мог бы наиболее легко воспроизвести его, открыв Action Center из меню "Пуск" (с меню "Пуск", настроенным для отображения элементов панели управления в меню).

Я подозреваю, что это поведение является следствием того, что Windows Explorer владеет окном рабочего стола, которое GUI рассматривает как частный случай.

Единственное, что странно, это то, что я не мог воспроизвести это поведение с помощью диалогового окна USB, о котором вы говорите, который (когда я его пробовал) генерировал отдельный процесс (экземпляр rundll32.exe). Это может зависеть от других факторов.

Ответ 2

Я не могу придумать способ протестировать это, что не сложнее, чем у меня есть время прямо сейчас, но внимательно посмотрев на документы SetForegroundWindow, http://msdn.microsoft.com/en-us/library/ms633539(VS.85).aspx, одним из условий, перечисленных в примечаниях относительно процессов, которые могут задавать передний план, является:

  • Процесс получил последнее событие ввода.

Если я ошибаюсь, проводник Windows получает все входные события, чтобы проверять наличие горячих клавиш, другие такие нажатия клавиш фокуса и щелчки мыши за пределами текущего окна и т.д.

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

Ответ 3

Как уже упоминалось, ввод окон разных потоков обрабатывается независимо. AttachThreadInput API позволяет обмениваться состояниями потоков, в частности:

Используя функцию AttachThreadInput, поток может присоединить свой вход обрабатывающий механизм к другому потоку. [...] Это также позволяет потокам для совместного использования своих входных состояний, поэтому они могут вызывать функцию SetFocus для установите фокус клавиатуры в окно другого потока.

Теперь, когда вы видите, какое окно в настоящее время находится на переднем плане, если вы делитесь своим состоянием потока с потоком окна переднего плана, ваш SetFocus будет украсть фокус оттуда.

CWindow Window = GetForegroundWindow();
if(Window)
{
  const DWORD nWindowThreadIdentifier = Window.GetWindowThreadID();
  const DWORD nThreadIdentifier = GetCurrentThreadId();
  AttachThreadInput(nThreadIdentifier, nWindowThreadIdentifier, TRUE);
  GetDlgItem(IDC_EDIT).SetFocus(); // This succeeds now as we are sharing thread state with foreground window
  AttachThreadInput(nThreadIdentifier, nWindowThreadIdentifier, FALSE);
  m_sAction = _T("Done");
} else
  m_sAction = _T("Nothing to do");

Смотрите также: фрагмент исходного кода, binary