Есть ли способ опустить окно из дублирования рабочего стола?
Я хотел бы иметь возможность показать окно, содержащее сообщение, которое отображается для пользователя, но которое не перехватывается при дублировании рабочего стола. Это возможно?
В качестве альтернативы, есть ли способ рисовать поверх поверхности рабочего стола, прежде чем он будет показан пользователю? (в идеале без массового останова графического процессора)
Предыстория: я пишу приложение для удаленного просмотра / поддержки и хочу, чтобы удаленный пользователь работал конфиденциально - закрывая экран пользователя, не мешая захвату.
Я бы не хотел возвращаться к темным дням WM_PRINT и BitBlt, но я не уверен, что DXGI позволяет то, что я хочу делать.
5 ответов
Дублирование рабочего стола копирует составное изображение, доставляемое на видеовыход, и ваша идея состоит в том, чтобы оно работало не только за исключением определенных областей, но и выполняло операции рендеринга / компоновки операционной системы для окон за рассматриваемым окном, компоновка которых не нужна для нормальная работа на рабочем столе. Такая композиция на самом деле не происходит в первую очередь, и Desktop Duplication не предлагает сервисы для ее принудительного или иного разделения данных изображения для каждого окна.
Это старый поток, но, тем не менее, если вы хотите скрыть окно от дублирования рабочего стола или API захвата графики Windows, вы можете использовать эту функцию.
[DllImport("user32.dll")]
private static extern uint SetWindowDisplayAffinity(IntPtr hwnd, uint dwAffinity);
public void HideWindowContent(IntPtr hwnd)
{
uint WDA_EXCLUDEFROMCAPTURE = 0x00000011;
var result = SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE);
}
ПРИМЕЧАНИЕ. Этот ответ решает проблему лишь частично.
Мы с @DanGroom хотели добиться захвата содержимого экрана, в то время как на экране постоянно отображается фиксированное изображение или растровое изображение, покрывающее содержимое экрана.
По предложению @RomanR. Я взглянул на API захвата экрана UWP, однако понял, что они применимы только к приложениям UWP. Я не пробовал их, но похоже, что они основаны на DirectX, как пример дублирования рабочего стола. Я не знаю DirectX, но, насколько я понимаю, нет способа контролировать конкретное окно с помощью этих API. Вы можете просто получить кадр всего экрана. Если экран покрыт полноэкранным окном, то это окно, которое захватывается в каждом кадре (я добавил кусок кода, чтобы сбросить кадр в bmp, и это было поведение). Таким образом, эти API привели к тупику в моем случае.
Вместо этого у меня был небольшой успех с DWM Thumbnail API (DwmRegisterThumbnail() и т. Д.). Они довольно просты по сравнению с API захвата экрана.
- Вы регистрируете окно, которое хотите контролировать, и задаете окно назначения, в которое хотите получать изображение контролируемого окна.
- Периодически запрашивать обновленное изображение отслеживаемого окна с помощьювызова DwmUpdateThumbnailProperties().
Здесь есть хороший образец
Большим преимуществом этих API является то, что они работают также с окнами, которые в настоящее время не отображаются или не закрыты другими окнами.Качество изображения и частота кадров приемлемы, и вы даже можете получить FullHD-миниатюру окна. Таким образом, вы можете просто создать самое верхнее полноэкранное окно и зарегистрировать его, чтобы получать "кадры" другого окна. Результатом является полноэкранное окно, в котором отображается содержимое другого окна.
Поскольку миниатюра отправляется в окно, это сводится к проблеме использования BitBlt для захвата изображения окна, которое принимает кадры другого окна. Это потому, что обновления экрана отправляются непосредственно в окно назначения, и, очевидно, вы не можете просто получать обновления кадров в буфере или что-то в этом роде. Если вы сделаете окно назначения прозрачным и попытаетесь захватить кадры, вы получите черное растровое изображение. Также вы можете застрять в таких проблемах с BitBlt.
Проблема может быть решена следующим образом:
- Перехватить сообщение (WM_... что-то), отправленное вызовом DwmUpdateThumbnailProperties, который также должен отправить кадр в окно назначения.
- Сохраните фрейм в любом формате и памяти непосредственно перед тем, как это будет отключено в окне
- Отправьте в окно назначения другой кадр (растровое изображение, используемое для покрытия экрана)
- Перейти к пункту 1
К сожалению, я не знаю, как этого добиться с помощью Windows API. Специально для пункта 2, как я могу получить кадр окна без захвата уже отображенного изображения?
Я немного покопался в сети и наткнулся на Magnification API. Я обнаружил, что обратный вызов может быть зарегистрирован для потока увеличения с помощью API MagSetImageScalingCallback.
Насколько я понимаю, этот обратный вызов вызывается всякий раз, когда необходимо нарисовать новый кадр в окне лупы, зарегистрированном с помощью MagSetWindowSource API. Необработанное растровое изображение экрана и вся соответствующая информация передаются в функцию обратного вызова, целью которой является преобразование растрового изображения, которое будет отображаться в окне после возврата из обратного вызова.
На мой взгляд, название " Обратный вызов масштабирования изображения " может привести к неправильному пониманию реального использования. В любом случае я наконец понял, как это можно использовать в моем приложении:
1) Окно лупы создается и устанавливается как верхний полноэкранный.
2) Обратный вызов вызывается, как только нужно нарисовать первый кадр
3) Исходное растровое изображение копируется в другой буфер
4) Исходное содержимое растрового изображения заменяется плоским черным растровым изображением.
5) Обратный вызов возвращается, и измененное растровое изображение отображается в окне лупы.
Этот шаг можно повторять без потери возможности "захвата". Фактически, даже если экран покрыт черным изображением, это не мешает Magnification API захватить экран.
Это потому, что окно, зарегистрированное как окно лупы, никогда не включается в захват (даже в случае полноэкранного окна).
Это именно то поведение, которое я искал. Я немного изменил образец снимка экрана, используя библиотеку увеличения на веб-сайте CodeProject, чтобы реализовать это поведение. Захваченные изображения, содержащиеся в указателе srcdata, выгружаются в набор файлов, чтобы продемонстрировать, что захват работает и что каждое изображение содержит обновленный захват.
К сожалению, эти API устарели, и замены пока нет.
Один из вариантов - реализовать протокол RDP самостоятельно и воспользоваться преимуществами функций, уже доступных в Windows, для блокировки локального сеанса при запуске удаленного сеанса. Вы можете заглянуть в mstsclib, если хотите сделать это самостоятельно, или вы можете проверить mRemoteNG, который представляет собой полнофункциональный клиент удаленного рабочего стола C#, который также реализует протокол.
Другой вариант - захватить окна, которые частично (или полностью) скрыты в выбранном вами контексте растрового изображения, вы можете использовать user32.dll PrintWindow
и малоизвестные PW_RENDERFULLCONTENT
флаг. Этот флаг доступен только в Windows 8.1 или новее, и он будет захватывать даже окна, которые отображаются с помощью Composition API или напрямую с помощью DirectX. Ниже приведен простой пример того, как вы можете это использовать:
Bitmap GetWindowBitmap(IntPtr hWnd) {
RECT bounds;
if (!GetWindowRect(hWnd, out bounds))
throw new Win32Exception();
var bmp = new Bitmap(bounds.right - bounds.left, bounds.bottom - bounds.top);
using (var g = Graphics.FromImage(bmp))
{
IntPtr dc = IntPtr.Zero;
try
{
dc = g.GetHdc();
bool success = PrintWindow(hWnd, dc, PrintWindowDrawingOptions.PW_RENDERFULLCONTENT /* 0x00000002 */);
if (!success)
throw new Win32Exception();
}
finally
{
if (dc != IntPtr.Zero)
g.ReleaseHdc(dc);
}
}
return bmp;
}
Вы можете перечислить все окна на рабочем столе, используя этот подход, но это будет не быстро, поскольку PrintWindow запрашивает каждое окно для перерисовки на предоставленный вами жесткий диск. Даже одно некорректное окно может замедлить это на сотни или тысячи миллисекунд.