FFMPEG录屏(20)--- 枚举macOS下的窗口和屏幕列表,并获取名称缩略图等信息

在 macOS 下获取可屏幕共享的窗口和屏幕

在 macOS 下,我们可以通过使用 Core Graphics 和 Cocoa 框架来获取当前系统中可屏幕共享的窗口和屏幕信息。本文将详细介绍如何获取窗口和屏幕的 ID、标题、坐标、进程图标和缩略图等信息。

前提条件

在开始之前,请确保您的开发环境满足以下条件:

  1. macOS 系统
  2. 安装了 Xcode 或其他支持 Objective-C++ 开发的编译器

步骤

1. 包含必要的头文件

首先,包含必要的头文件:

objective-cpp 复制代码
#include "base/devices/screen/enumerator.h"
#include "base/devices/screen/darwin/desktop_configuration.h"
#include "base/devices/screen/desktop_geometry.h"
#include "base/devices/screen/utils.h"
#include "base/folder/folder.h"
#include "base/log/logger.h"

#include <vector>
#include <functional>
#include <stdlib.h>
#include <string.h>

#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
2. 获取窗口信息

我们可以使用 CGWindowListCopyWindowInfo 函数获取当前系统中的窗口信息。以下是获取窗口信息的函数:

objective-cpp 复制代码
int enum_windows(const traa_size icon_size, const traa_size thumbnail_size,
                 const unsigned int external_flags, std::vector<traa_screen_source_info> &infos) {
  CFArrayRef window_array = CGWindowListCopyWindowInfo(
      kCGWindowListOptionAll | kCGWindowListExcludeDesktopElements, kCGNullWindowID);
  if (!window_array)
    return false;

  MacDesktopConfiguration desktop_config =
      MacDesktopConfiguration::GetCurrent(MacDesktopConfiguration::TopLeftOrigin);

  CFIndex count = CFArrayGetCount(window_array);
  for (CFIndex i = 0; i < count; i++) {
    CFDictionaryRef window =
        reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(window_array, i));
    if (!window) {
      continue;
    }

    CFNumberRef window_id =
        reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowNumber));
    if (!window_id) {
      continue;
    }

    traa_screen_source_info window_info;
    window_info.is_window = true;

    if (!CFNumberGetValue(window_id, kCFNumberSInt64Type, &window_info.id) || window_info.id <= 0) {
      continue;
    }

    CFNumberRef window_layer =
        reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowLayer));
    if (!window_layer) {
      continue;
    }

    int layer;
    if (!CFNumberGetValue(window_layer, kCFNumberIntType, &layer)
        || layer < kCGNormalWindowLevel || layer == kCGDockWindowLevel) {
      continue;
    }

    window_info.is_minimized = !is_window_on_screen(window);
    window_info.is_maximized = is_window_full_screen(desktop_config, window);

    if ((external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_MINIMIZED) && window_info.is_minimized) {
      continue;
    }

    CFNumberRef window_alpha =
        reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowAlpha));
    if (!window_alpha) {
      continue;
    } else {
      int alpha;
      if (!CFNumberGetValue(window_alpha, kCFNumberIntType, &alpha) || alpha <= 0) {
        continue;
      }
    }

    CFNumberRef window_sharing_state =
        reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowSharingState));
    if (!window_sharing_state) {
      continue;
    } else {
      int sharing_state;
      if (!CFNumberGetValue(window_sharing_state, kCFNumberIntType, &sharing_state) ||
          sharing_state != 1) {
        continue;
      }
    }

    CFNumberRef window_pid =
        reinterpret_cast<CFNumberRef>(CFDictionaryGetValue(window, kCGWindowOwnerPID));
    if (!window_pid) {
      continue;
    }

    std::string str_title = get_window_title(window);
    std::string str_owner_name = get_window_owner_name(window);
    if (str_title.empty() && str_owner_name.empty() &&
        !(external_flags & TRAA_SCREEN_SOURCE_FLAG_NOT_IGNORE_UNTITLED)) {
      continue;
    }
    if (str_title.empty()) {
      str_title = std::move(str_owner_name);
    }
    if (!str_title.empty()) {
      strncpy(const_cast<char *>(window_info.title), str_title.c_str(),
              std::min(sizeof(window_info.title) - 1, str_title.size()));
    }

    CGRect window_bounds = CGRectZero;
    if (CFDictionaryContainsKey(window, kCGWindowBounds)) {
      CFDictionaryRef rect = (CFDictionaryRef)CFDictionaryGetValue(window, kCGWindowBounds);
      if (rect) {
        CGRectMakeWithDictionaryRepresentation(rect, &window_bounds);
      }
    }
    if (window_bounds.size.width <= 96 || window_bounds.size.height <= 96) {
      continue;
    }
    window_info.rect = desktop_rect::make_xywh(window_bounds.origin.x, window_bounds.origin.y,
                                               window_bounds.size.width, window_bounds.size.height)
                           .to_traa_rect();

    NSRunningApplication *running_app = [NSRunningApplication
        runningApplicationWithProcessIdentifier:((__bridge NSNumber *)window_pid).intValue];
    if (running_app) {
      std::string process_path = [[running_app bundleURL] fileSystemRepresentation];
      if (!process_path.empty()) {
        strncpy(const_cast<char *>(window_info.process_path), process_path.c_str(),
                std::min(sizeof(window_info.process_path) - 1, process_path.size()));
      } else if (external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_NOPROCESS_PATH) {
        continue;
      }

      if (icon_size.width > 0 && icon_size.height > 0 && running_app.icon &&
          running_app.icon.representations.count > 0) {
        NSImage *icon = running_app.icon;
        NSBitmapImageRep *icon_rep =
            [[NSBitmapImageRep alloc] initWithData:[icon TIFFRepresentation]];
        if (icon_rep) {
          desktop_size icon_scaled_size =
              calc_scaled_size(desktop_size(icon_rep.size.width, icon_rep.size.height), icon_size);
          CGSize icon_scaled_ns_size =
              CGSizeMake(icon_scaled_size.width(), icon_scaled_size.height());
          NSBitmapImageRep *icon_scaled_image_rep = scale_image(icon_rep, icon_scaled_ns_size);
          if (icon_scaled_image_rep) {
            size_t icon_data_size = [icon_scaled_image_rep pixelsWide] *
                                    [icon_scaled_image_rep pixelsHigh] *
                                    [icon_scaled_image_rep samplesPerPixel] * sizeof(unsigned char);
            window_info.icon_data = new uint8_t[icon_data_size];
            window_info.icon_size = icon_scaled_size.to_traa_size();
            if (!window_info.icon_data) {
              LOG_ERROR("alloc memory for icon data failed");
              continue;
            }
            memcpy(const_cast<uint8_t *>(window_info.icon_data), [icon_scaled_image_rep bitmapData],
                   icon_data_size);
          }
        }
      }
    }

    NSBitmapImageRep *origin_image_rep =
        get_image_rep(true, static_cast<CGWindowID>(window_info.id));
    if (!origin_image_rep) {
      continue;
    }

    if (thumbnail_size.width > 0 && thumbnail_size.height > 0) {
      NSImage *origin_image = [NSImage new];
      [origin_image addRepresentation:origin_image_rep];
      if (origin_image.size.width <= 1 || origin_image.size.height <= 1) {
        continue;
      }

      desktop_size scaled_size = calc_scaled_size(
          desktop_size(origin_image.size.width, origin_image.size.height), thumbnail_size);
      CGSize scaled_ns_size = CGSizeMake(scaled_size.width(), scaled_size.height());
      NSBitmapImageRep *scaled_image_rep = scale_image(origin_image_rep, scaled_ns_size);
      if (!scaled_image_rep) {
        continue;
      }

      size_t thumbnail_data_size = [scaled_image_rep pixelsWide] * [scaled_image_rep pixelsHigh] *
                                   [scaled_image_rep samplesPerPixel] * sizeof(unsigned char);
      window_info.thumbnail_data = new uint8_t[thumbnail_data_size];
      window_info.thumbnail_size = scaled_size.to_traa_size();
      if (!window_info.thumbnail_data) {
        LOG_ERROR("alloc memory for thumbnail data failed");
        continue;
      }
      memcpy(const_cast<uint8_t *>(window_info.thumbnail_data), [scaled_image_rep bitmapData],
             thumbnail_data_size);
    }

    infos.push_back(window_info);
  }

  CFRelease(window_array);
  return TRAA_ERROR_NONE;
}
3. 获取屏幕信息

我们可以使用 NSScreen 类获取当前系统中的屏幕信息。以下是获取屏幕信息的函数:

objective-cpp 复制代码
int enum_screens(const traa_size icon_size, const traa_size thumbnail_size,
                 const unsigned int external_flags, std::vector<traa_screen_source_info> &infos) {
  NSArray *screen_array = NSScreen.screens;
  if (!screen_array) {
    return TRAA_ERROR_NONE;
  }

  for (size_t i = 0; i < screen_array.count; i++) {
    NSScreen *screen = screen_array[i];
    if (!screen) {
      continue;
    }

    CGDirectDisplayID screen_id = static_cast<CGDirectDisplayID>(
        [[screen.deviceDescription objectForKey:@"NSScreenNumber"] unsignedIntValue]);
    CGRect screen_bounds = CGDisplayBounds(screen_id);

    NSBitmapImageRep *screen_image_rep = get_image_rep(false, screen_id);
    if (!screen_image_rep) {
      continue;
    }

    traa_screen_source_info screen_info;
    screen_info.is_window = false;
    screen_info.is_primary = CGDisplayIsMain(screen_id);
    screen_info.id = static_cast<int64_t>(screen_id);
    snprintf(const_cast<char *>(screen_info.title), sizeof(screen_info.title), "Display %d",
             static_cast<int>(i));
    screen_info.rect = desktop_rect::make_xywh(screen_bounds.origin.x, screen_bounds.origin.y,
                                               screen_bounds.size.width, screen_bounds.size.height)
                           .to_traa_rect();

    if (thumbnail_size.width > 0 && thumbnail_size.height > 0) {
      NSImage *screen_image = [NSImage new];
      [screen_image addRepresentation:screen_image_rep];

      desktop_size scaled_size = calc_scaled_size(
          desktop_size(screen_image.size.width, screen_image.size.height), thumbnail_size);
      CGSize scaled_ns_size = CGSizeMake(scaled_size.width(), scaled_size.height());

      NSBitmapImageRep *scaled_image_rep = scale_image(screen_image_rep, scaled_ns_size);
      if (!scaled_image_rep) {
        continue;
      }

      size_t thumbnail_data_size = [scaled_image_rep pixelsWide] * [scaled_image_rep pixelsHigh] *
                                   [scaled_image_rep samplesPerPixel] * sizeof(unsigned char);
      screen_info.thumbnail_data = new uint8_t[thumbnail_data_size];
      screen_info.thumbnail_size = scaled_size.to_traa_size();
      if (!screen_info.thumbnail_data) {
        LOG_ERROR("alloc memory for thumbnail data failed");
        continue;
      }
      memcpy(const_cast<uint8_t *>(screen_info.thumbnail_data), [scaled_image_rep bitmapData],
             thumbnail_data_size);
    }

    infos.push_back(screen_info);
  }

  return TRAA_ERROR_NONE;
}
4. 枚举屏幕和窗口信息

最后,我们可以通过调用 enum_windowsenum_screens 函数来枚举当前系统中的窗口和屏幕信息:

objective-cpp 复制代码
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) {
  if (__builtin_available(macOS 11, *)) {
    if (!CGPreflightScreenCaptureAccess()) {
      CGRequestScreenCaptureAccess();
      return TRAA_ERROR_PERMISSION_DENIED;
    }
  }

  std::vector<traa_screen_source_info> sources;

  if (!(external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_WINDOW)) {
    enum_windows(icon_size, thumbnail_size, external_flags, sources);
  }

  if (!(external_flags & TRAA_SCREEN_SOURCE_FLAG_IGNORE_SCREEN)) {
    enum_screens(icon_size, thumbnail_size, external_flags, sources);
  }

  if (sources.size() == 0) {
    return traa_error::TRAA_ERROR_NONE;
  }

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

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

    if (std::strlen(source_info.process_path) > 0) {
      strncpy(const_cast<char *>(dest_info.process_path), source_info.process_path,
              std::min(sizeof(dest_info.process_path) - 1, std::strlen(source_info.process_path)));
    }
  }

  return TRAA_ERROR_NONE;
}

总结

通过上述步骤,我们可以在 macOS 下获取当前系统中可屏幕共享的窗口和屏幕信息,包括窗口和屏幕的 ID、标题、坐标、进程图标和缩略图等信息。希望这篇文章对您有所帮助。

源码传送

traa

相关推荐
blanks202019 小时前
ffmpeg 学习笔记 通过命令行采集音频
ffmpeg
fthux1 天前
如果你用 Mac,那你可能需要 Noti Shift
macos·开源·github
counterxing4 天前
最近发现一个 Mac 工具,有点像把 Raycast、语音输入法、截图和录屏塞到了一起
macos·ai编程·claude
Mahut5 天前
我用 Electron + FFmpeg 做了一个本地视频处理工作站 ClipForge
前端·ffmpeg·electron
元Y亨H11 天前
MacBook Air 开发神器:IDEA 与 PyCharm 极简安装及环境配置
macos
yuanyxh12 天前
macOS 应用 - 纯对话生成
前端·macos·ai编程
AI创界者14 天前
PilotTTS 一键整合包(Win/Mac):8G 显存畅跑,实测解锁情绪与副语言的精准控制
人工智能·macos·aigc·音视频
AirDroid_cn14 天前
系统终端与iTerm2字体看起来不一样?macOS Sequoia统一渲染指南
macos
源之缘-OFD先行者14 天前
破界渲染:WinForm下的FFmpeg+Vortice极速推流引擎
ffmpeg·winform·推流·h264
源来猿往14 天前
记ffmpeg-8.1.1 之Android库编译(window)
android·ffmpeg