Bootstrap

FFMPEG录屏(18)--- 枚举Windows下的窗口列表并获取进程图标、标题、缩略图等

在Windows中获取可进行屏幕共享捕获的窗口列表及其图标、缩略图

在Windows系统中,获取可进行屏幕共享捕获的窗口列表以及它们的图标和缩略图是一个复杂但有趣的过程。本文将详细介绍如何实现这一功能,涉及到的主要技术包括Windows API、C++编程和一些第三方库。

前置知识

在开始之前,您需要了解以下内容:

  1. Windows API:Windows API提供了大量的函数,用于与操作系统进行交互。
  2. C++编程:本文的示例代码使用C++编写。
  3. 第三方库:我们将使用libyuv库来处理图像缩放。

实现步骤

1. 包含必要的头文件

首先,我们需要包含一些必要的头文件,这些头文件提供了我们需要的函数和数据结构。

#include "base/devices/screen/desktop_geometry.h"
#include "base/devices/screen/enumerator.h"
#include "base/devices/screen/mouse_cursor.h"
#include "base/devices/screen/utils.h"
#include "base/devices/screen/win/capture_utils.h"
#include "base/devices/screen/win/cursor.h"
#include "base/devices/screen/win/scoped_object_gdi.h"
#include "base/log/logger.h"
#include "base/strings/string_trans.h"
#include "base/utils/win/version.h"

#include <libyuv/scale_argb.h>

#include <memory>
#include <string>

#include <stdlib.h>

#include <shellapi.h>
#include <windows.h>

2. 定义辅助函数

我们需要一些辅助函数来获取窗口属性、窗口文本、进程路径等。

获取窗口属性
typedef HRESULT(WINAPI *FuncDwmGetWindowAttribute)(HWND window, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute);

FuncDwmGetWindowAttribute helper_get_dwmapi_get_window_attribute() {
  HINSTANCE dwmapi = LoadLibraryW(L"Dwmapi.dll");
  if (dwmapi == nullptr) {
    return nullptr;
  }

  FuncDwmGetWindowAttribute dwmapi_get_window_attribute =
      (FuncDwmGetWindowAttribute)GetProcAddress(dwmapi, "DwmGetWindowAttribute");
  if (dwmapi_get_window_attribute == nullptr) {
    return nullptr;
  }

  return dwmapi_get_window_attribute;
}
获取窗口文本
int get_window_text_safe(HWND window, LPWSTR p_string, int cch_max_count) {
  return ::InternalGetWindowText(window, p_string, cch_max_count);
}
获取进程路径
int get_window_process_path(HWND window, wchar_t *path, int max_count) {
  DWORD process_id;
  ::GetWindowThreadProcessId(window, &process_id);
  if (process_id == 0) {
    return 0;
  }

  HANDLE process = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
  if (process == nullptr) {
    return 0;
  }

  DWORD buffer_size = static_cast<DWORD>(max_count);
  if (::QueryFullProcessImageNameW(process, 0, path, &buffer_size) == 0) {
    ::CloseHandle(process);
    return 0;
  }

  ::CloseHandle(process);
  return buffer_size;
}

3. 定义窗口枚举回调函数

我们需要一个回调函数来处理每个被枚举到的窗口。

BOOL WINAPI enum_screen_source_info_proc(HWND window, LPARAM lParam) {
  auto *param = reinterpret_cast<enumerator_param *>(lParam);

  if (!::IsWindowVisible(window) || ::IsIconic(window) || ::GetShellWindow() == window) {
    return TRUE;
  }

  if (::GetAncestor(window, GA_ROOT) != window) {
    return TRUE;
  }

  desktop_rect window_rect = desktop_rect::make_ltrb(0, 0, 0, 0);
  if (!get_window_rect(window, &window_rect) || window_rect.is_empty()) {
    return TRUE;
  }

  if (is_window_invisible_win10_background_app(window)) {
    return TRUE;
  }

  HWND owner = ::GetWindow(window, GW_OWNER);
  LONG exstyle = ::GetWindowLongW(window, GWL_EXSTYLE);
  if (owner && !(exstyle & WS_EX_APPWINDOW)) {
    return TRUE;
  }

  if ((exstyle & WS_EX_TOOLWINDOW) &&
      !(param->external_flags & TRAA_SCREEN_SOURCE_FLAG_NOT_IGNORE_TOOLWINDOW)) {
    return TRUE;
  }

  if (!capture_utils::is_window_response(window) &&
      !(param->external_flags & TRAA_SCREEN_SOURCE_FLAG_NOT_IGNORE_UNRESPONSIVE)) {
    return TRUE;
  }

  bool owned_by_current_process = capture_utils::is_window_owned_by_current_process(window);
  if ((param->external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_CURRENT_PROCESS) &&
      owned_by_current_process) {
    return TRUE;
  }

  bool has_title = false;
  WCHAR window_title[TRAA_MAX_DEVICE_NAME_LENGTH] = L"";
  if (get_window_text_safe(window, window_title, TRAA_MAX_DEVICE_NAME_LENGTH - 1) > 0) {
    has_title = true;
  } else {
    LOG_ERROR("get window title failed: {}", ::GetLastError());
  }

  if (!has_title && !(param->external_flags & TRAA_SCREEN_SOURCE_FLAG_NOT_IGNORE_UNTITLED)) {
    return TRUE;
  }

  bool has_process_path = false;
  WCHAR process_path[TRAA_MAX_DEVICE_NAME_LENGTH] = L"";
  if (get_window_process_path(window, process_path, TRAA_MAX_DEVICE_NAME_LENGTH - 1) > 0) {
    has_process_path = true;
  } else {
    LOG_ERROR("get window process path failed: {}", ::GetLastError());
  }

  if ((param->external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_NOPROCESS_PATH) &&
      !has_process_path) {
    return TRUE;
  }

  WCHAR class_name[TRAA_MAX_DEVICE_NAME_LENGTH] = L"";
  const int class_name_length = ::GetClassNameW(window, class_name, TRAA_MAX_DEVICE_NAME_LENGTH);
  if (class_name_length < 1)
    return TRUE;

  if (!(param->external_flags & TRAA_SCREEN_SOURCE_FLAG_NOT_SKIP_SYSTEM_WINDOWS)) {
    if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Program Manager") == 0)
      return TRUE;

    if (wcscmp(class_name, L"TaskManagerWindow") == 0)
      return TRUE;

    if (wcscmp(class_name, L"Button") == 0)
      return TRUE;

    if (wcscmp(class_name, L"Windows.Internal.Shell.TabProxyWindow") == 0)
      return TRUE;
  }

  traa_screen_source_info window_info;
  window_info.id = reinterpret_cast<int64_t>(window);
  window_info.screen_id = get_window_owned_screen_id(window);
  window_info.is_window = true;
  window_info.is_minimized = ::IsIconic(window);

  if (is_window_maximized(window, &window_info.is_maximized) && window_info.is_maximized) {
    get_window_maximized_rect(window, &window_rect);
  }
  window_info.rect = window_rect.to_traa_rect();
  window_info.icon_size = param->icon_size;
  window_info.thumbnail_size = param->thumbnail_size;

  if (has_title) {
    auto utf8_title = string_trans::unicode_to_utf8(window_title);
    strncpy_s(const_cast<char *>(window_info.title), sizeof(window_info.title) - 1,
              utf8_title.c_str(), utf8_title.length());
  }

  if (has_process_path) {
    auto utf8_process_path = string_trans::unicode_to_utf8(process_path);
    strncpy_s(const_cast<char *>(window_info.process_path), sizeof(window_info.process_path) - 1,
              utf8_process_path.c_str(), utf8_process_path.length());
  }

  if (has_process_path && param->icon_size.width > 0 && param->icon_size.height > 0) {
    if (get_process_icon_data(
            process_path, desktop_size(param->icon_size.width, param->icon_size.height),
            const_cast<uint8_t **>(&window_info.icon_data), window_info.icon_size)) {
    } else {
      LOG_ERROR("get icon data failed");
    }
  }

  if (param->thumbnail_size.width > 0 && param->thumbnail_size.height > 0 &&
      param->thumbnail_instance) {
    if (!param->thumbnail_instance->get_thumbnail_data(
            window, param->thumbnail_size, const_cast<uint8_t **>(&window_info.thumbnail_data),
            window_info.thumbnail_size)) {
      LOG_ERROR("get thumbnail data failed");
    }
  }

  param->infos.push_back(window_info);

  return TRUE;
}

4. 枚举窗口信息

最后,我们需要一个函数来枚举所有窗口的信息。

int screen_source_info_enumerator::enum_screen_source_info(const traa_size icon_size,
                                                           const traa_size thumbnail_size,
                                                           const unsigned int external_flags,
                                                           traa_screen_source_info **infos,
                                                           int *count) {
  std::unique_ptr<thumbnail> thumbnail_instance;
  if (thumbnail_size.width > 0 && thumbnail_size.height > 0) {
    thumbnail_instance.reset(new thumbnail());
  }

  enumerator_param param = {
      icon_size, thumbnail_size, external_flags, {}, thumbnail_instance.get()};

  BOOL ret = ::EnumWindows(enum_screen_source_info_proc, reinterpret_cast<LPARAM>(&param));
  if (!ret) {
    LOG_ERROR("call ::EnumWindows failed: {}", ::GetLastError());
    return traa_error::TRAA_ERROR_ENUM_SCREEN_SOURCE_INFO_FAILED;
  }

  *count = static_cast<int>(param.infos.size());
  *infos =
      reinterpret_cast<traa_screen_source_info *>(new traa_screen_source_info[param.infos.size()]);
  if (*infos == nullptr) {
    LOG_ERROR("alloca memroy for infos failed: {}", ::GetLastError());
    return traa_error::TRAA_ERROR_OUT_OF_MEMORY;
  }

  for (size_t i = 0; i < param.infos.size(); ++i) {
    auto &source_info = param.infos[i];
    auto &dest_info = (*infos)[i];
    memcpy(&dest_info, &source_info, sizeof(traa_screen_source_info));
    strncpy_s(const_cast<char *>(dest_info.title), sizeof(dest_info.title) - 1, source_info.title,
              std::strlen(source_info.title));
    strncpy_s(const_cast<char *>(dest_info.process_path), sizeof(dest_info.process_path) - 1,
              source_info.process_path, std::strlen(source_info.process_path));
  }

  return traa_error::TRAA_ERROR_NONE;
}

总结

通过上述步骤,我们可以在Windows系统中获取可进行屏幕共享捕获的窗口列表,并获取它们的图标和缩略图。这一过程涉及到Windows API的使用、窗口属性的获取、图标和缩略图的处理等多个方面。希望本文能对您有所帮助。

最近有点懒了,这还是copilot生成的。。。

源码传送

traa

;