#include "DesktopToolsMacOs.hpp" #include "config.h" #import #import #include #include #include #include void DesktopTools::init(){ } void DesktopTools::requestPermissions(){ // Request permissions if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized) { qInfo() << "Requesting Video permission"; [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { qInfo() << "Video permission has" << (granted ? "" : " not") << " been granted"; }]; } else qInfo() << "Video permission is already granted"; if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio] != AVAuthorizationStatusAuthorized){ qInfo() << "Requesting Audio permission"; [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) { qInfo() << "Audio permission has" << (granted ? "" : " not") << " been granted"; }]; } else qInfo() << "Audio permission is already granted"; } bool isWindowMinimized(CGWindowID id) { CFArrayRef ids = CFArrayCreate(NULL, reinterpret_cast(&id), 1, NULL); CFArrayRef descriptions = CGWindowListCreateDescriptionFromArray(ids); bool minimized = false; if (descriptions && CFArrayGetCount(descriptions)) { CFDictionaryRef window = reinterpret_cast(CFArrayGetValueAtIndex(descriptions, 0)); minimized = ! reinterpret_cast(CFDictionaryGetValue(window, kCGWindowIsOnscreen)); } CFRelease(descriptions); CFRelease(ids); return minimized; } QList DesktopTools::getWindows() { QList windows; bool haveAccess = CGPreflightScreenCaptureAccess(); //Requests event listening access if absent, potentially prompting if(!haveAccess) haveAccess = CGRequestScreenCaptureAccess(); CFArrayRef infos = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID); for(int windowIndex = 0 ; windowIndex < CFArrayGetCount(infos) ; ++windowIndex) { CFDictionaryRef description = (CFDictionaryRef)CFArrayGetValueAtIndex ((CFArrayRef)infos, windowIndex); if(CFDictionaryContainsKey(description, kCGWindowNumber) && CFDictionaryContainsKey(description, kCGWindowLayer) && CFDictionaryContainsKey(description, kCGWindowName) ) { CFNumberRef idRef = (CFNumberRef)CFDictionaryGetValue (description, kCGWindowNumber); CFNumberRef layerRef = (CFNumberRef)CFDictionaryGetValue (description, kCGWindowLayer); CFStringRef titleRef = (CFStringRef)CFDictionaryGetValue (description, kCGWindowName); CGWindowID id=0, layer=0; if (!CFNumberGetValue(idRef, kCFNumberIntType, &id) || !CFNumberGetValue(layerRef, kCFNumberIntType, &layer)) { continue; } QVariantMap window; if(CFDictionaryContainsKey(description, kCGWindowName)) { window["name"] = QString::fromCFString(titleRef); } if( window["name"] == "") continue; window["windowId"] = id; // Skip layer != 0 like menu or dock if(layer != 0 || isWindowMinimized(id)) continue; // Remove System status indicator from the list as they are on layer 0 CFStringRef ownerName = reinterpret_cast(CFDictionaryGetValue(description, kCGWindowOwnerName)); if (titleRef && CFEqual(titleRef, CFSTR("StatusIndicator")) && ownerName && CFEqual(ownerName, CFSTR("Window Server"))) { continue; } windows << window; } } if(infos) CFRelease(infos); return windows; } CGBitmapInfo CGBitmapInfoForQImage(const QImage &image) { CGBitmapInfo bitmapInfo = kCGImageAlphaNone; switch (image.format()) { case QImage::Format_ARGB32: bitmapInfo = kCGImageAlphaFirst | kCGBitmapByteOrder32Host; break; case QImage::Format_RGB32: bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host; break; case QImage::Format_RGBA8888_Premultiplied: bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big; break; case QImage::Format_RGBA8888: bitmapInfo = kCGImageAlphaLast | kCGBitmapByteOrder32Big; break; case QImage::Format_RGBX8888: bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big; break; case QImage::Format_ARGB32_Premultiplied: bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; break; default: break; } return bitmapInfo; } QImage CGImageToQImage(CGImageRef cgImage) { const size_t width = CGImageGetWidth(cgImage); const size_t height = CGImageGetHeight(cgImage); QImage image(width, height, QImage::Format_ARGB32_Premultiplied); image.fill(Qt::transparent); CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); CGContextRef context = CGBitmapContextCreate((void *)image.bits(), image.width(), image.height(), 8, image.bytesPerLine(), colorSpace, CGBitmapInfoForQImage(image)); // Scale the context so that painting happens in device-independent pixels const qreal devicePixelRatio = image.devicePixelRatio(); CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio); CGRect rect = CGRectMake(0, 0, width, height); CGContextDrawImage(context, rect, cgImage); if(colorSpace) CFRelease(colorSpace); if(context) CFRelease(context); return image; } QImage DesktopTools::getWindowIcon(void *window) { CGWindowID windowId = *(CGWindowID*)&window; pid_t pid=0; CFArrayRef infos = CGWindowListCopyWindowInfo(kCGWindowListOptionIncludingWindow, windowId); NSString *bundleIdentifier = @""; if (infos && CFArrayGetCount(infos)) { CFDictionaryRef w = reinterpret_cast(CFArrayGetValueAtIndex(infos, 0)); CFNumberRef pidRef = reinterpret_cast(CFDictionaryGetValue(w, kCGWindowOwnerPID)); CFNumberGetValue(pidRef, kCFNumberIntType, &pid) ; bundleIdentifier = [[NSRunningApplication runningApplicationWithProcessIdentifier:pid] bundleIdentifier]; } if(infos) CFRelease(infos); if(bundleIdentifier) { NSURL* url = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:bundleIdentifier]; NSString * noScheme = [[url absoluteString] substringFromIndex:7]; NSImage *nsImage = [[NSWorkspace sharedWorkspace] iconForFile:noScheme]; NSRect rect = NSMakeRect(0, 0, nsImage.size.width, nsImage.size.height); CGImageRef cgImage = [nsImage CGImageForProposedRect:&rect context:NULL hints:nil]; return CGImageToQImage(cgImage); }else { qWarning() << "Screensharing : Bundle identifier is null for CGID=" << windowId << " pid="<< pid; return QImage(); } } QImage DesktopTools::takeScreenshot(void *window) { #if __MAC_OS_X_VERSION_MIN_REQUIRED > 140000 __block bool haveAccess = false; // Must be call from main thread! If not, you may be in deadlock. dispatch_sync(dispatch_get_main_queue(), ^{ // Checks whether the current process already has screen capture access haveAccess = CGPreflightScreenCaptureAccess(); //Requests event listening access if absent, potentially prompting if(!haveAccess) haveAccess = CGRequestScreenCaptureAccess(); }); if(!haveAccess){ qCritical() << "The application don't have the permission to capture the screen"; return QImage(); } std::condition_variable condition; std::mutex lock; BOOL ended = FALSE; __block SCContentFilter *filter = nil; __block CGImageRef capture = nil; __block BOOL *_ended = &ended; __block std::condition_variable *_condition = &condition; CGWindowID windowId = *(CGWindowID*)&window; [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent *shareableContent, NSError *error) { if (!error || error.code == 0) { if(windowId) { for (int i = 0; i < shareableContent.windows.count && !filter; ++i){ if (shareableContent.windows[i].windowID == windowId) { filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:shareableContent.windows[i]]; } } } }else qCritical() << "SCShareableContent error: " << error.code; *_ended = TRUE; _condition->notify_all(); }]; std::unique_lock locker(lock); condition.wait(locker, [&ended]{ return ended; }); if(!filter){ qCritical() << "Screenshot: The filter could not be created"; return QImage(); } *_ended = FALSE; SCStreamConfiguration *configuration = [[SCStreamConfiguration alloc] init]; configuration.capturesAudio = NO; configuration.excludesCurrentProcessAudio = YES; configuration.preservesAspectRatio = YES; configuration.showsCursor = NO; configuration.captureResolution = SCCaptureResolutionBest; configuration.width = NSWidth(filter.contentRect) * filter.pointPixelScale; configuration.height = NSHeight(filter.contentRect) * filter.pointPixelScale; [SCScreenshotManager captureImageWithFilter:filter configuration:configuration completionHandler:^(CGImageRef sampleBuffer, NSError *error){ if (!error || error.code == 0) { capture = sampleBuffer; [capture retain]; }else{ qCritical() << "Screenshot: cannot capture: " << error.code; } *_ended = TRUE; _condition->notify_all(); } ]; condition.wait(locker, [&ended]{ return ended; }); [configuration dealloc]; [filter dealloc]; if(!capture) { return QImage(); } QImage image = CGImageToQImage(capture); [capture release]; return image; // Deprecated: #else #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGWindowID windowId = *(CGWindowID*)&window; CGImageRef capture = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, windowId, kCGWindowImageBoundsIgnoreFraming); #pragma clang diagnostic pop return CGImageToQImage(capture); #endif } void *DesktopTools::getDisplay(int screenIndex){ Q_UNUSED(screenIndex) #ifdef ENABLE_SCREENSHARING CGDirectDisplayID displays[screenIndex+1]; CGDisplayCount displayCount; CGGetOnlineDisplayList(screenIndex+1, displays, &displayCount); CGDirectDisplayID display = 0; if(displayCount > screenIndex) display = displays[screenIndex]; return reinterpret_cast(display); #else return NULL; #endif } int DesktopTools::getDisplayIndex(void* screenSharing){ Q_UNUSED(screenSharing) #ifdef ENABLE_SCREENSHARING CGDirectDisplayID displayId = *(CGDirectDisplayID*)&screenSharing; int maxDisplayCount = 10; CGDisplayCount displayCount; do { CGDirectDisplayID displays[maxDisplayCount]; CGGetOnlineDisplayList(maxDisplayCount, displays, &displayCount); for(int i = 0 ; i < displayCount ; ++i) if( displays[i] == displayId) { return i; } maxDisplayCount *= 2; }while(displayCount == maxDisplayCount/2); #endif return 0; } QRect DesktopTools::getWindowGeometry(void* screenSharing) { Q_UNUSED(screenSharing) QRect result; #ifdef ENABLE_SCREENSHARING CGWindowID windowId = *(CGWindowID*)&screenSharing; CFArrayRef descriptions = CGWindowListCopyWindowInfo(kCGWindowListOptionIncludingWindow, windowId); if(CFArrayGetCount(descriptions) > 0) { CFDictionaryRef description = (CFDictionaryRef)CFArrayGetValueAtIndex ((CFArrayRef)descriptions, 0); if(CFDictionaryContainsKey(description, kCGWindowBounds)) { CFDictionaryRef bounds = (CFDictionaryRef)CFDictionaryGetValue (description, kCGWindowBounds); if(bounds) { CGRect windowRect; CGRectMakeWithDictionaryRepresentation(bounds, &windowRect); result = QRect(windowRect.origin.x, windowRect.origin.y, windowRect.size.width, windowRect.size.height); }else qWarning() << "Bounds found be cannot be parsed for Window ID : " << windowId; }else qWarning() << "No bounds specified in Apple description for Window ID : " << windowId; }else qWarning() << "No description found for Window ID : " << windowId; if(descriptions) CFRelease(descriptions); #endif return result; }