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

相关推荐
名字不要太长 像我这样就好1 小时前
【iOS】继承链
macos·ios·cocoa
潜龙95272 小时前
第4.3节 iOS App生成追溯关系
macos·ios·cocoa
肥or胖11 小时前
【FFmpeg 快速入门】本地播放器 项目
开发语言·qt·ffmpeg·音视频
笑虾11 小时前
bat 批处理实现 FFmpeg 命令导出 mov 到 png 序列帧
ffmpeg·png·mov·序列帧
玄辰星君11 小时前
【MAC】nacos 2.5.1容器docker安装
macos·docker·nacos
atwdy16 小时前
MacOS安装linux虚拟机
linux·运维·ubuntu·macos·utm
echola_mendes17 小时前
Dify:在MacOS系统下Dify的本地部署与使用
macos
Tim风声(网络工程师)17 小时前
如何通过mac的前24bit,模糊确认是那一台什么样的设备
运维·服务器·网络·macos
菜鸟555551 天前
河南萌新联赛2025第一场-河南工业大学
macos·objective-c·cocoa
mortimer2 天前
当AI配音遇上视频:实现音画同步的自动化工程实践
python·ffmpeg·ai编程