Обнаруживать, когда пользователь делает снимок экрана

Я ищу способ для моего приложения получать уведомление, когда пользователь делает снимок экрана либо с Command-Shift-3, либо Command-Shift-4.

Примером этого могут служить приложения, такие как Droplr и Cloud App, которые автоматически загружают снимок экрана.

Я искал вокруг и узнал, что он может иметь какое-то отношение к уведомлениям Darwin, но я не уверен, с чего начать.

Ответ 1

Это было упомянуто в одном из предыдущих комментариев, но вы можете использовать NSMetadataQuery для поиска файлов, где kMDItemIsScreenCapture = 1. Это специальный атрибут, который добавляется к файлам скриншотов.

Я просто взломал небольшую демонстрацию, показывающую, как это сделать, и разместил ее на github:

https://github.com/davedelong/Demos/blob/master/ScreenShot%20Detector

Ответ 2

Вот как я это сделал, это немного сложно, но я постараюсь сделать это шаг за шагом:


Прежде чем начать, в файле заголовка объявите следующие переменные и методы:

BOOL shouldObserveDesktop;
NSDictionary *knownScreenshotsOnDesktop;
NSString *screenshotLocation;
NSString *screenshotFilenameSuffix;

- (void)startObservingDesktop;
- (void)stopObservingDesktop;
- (NSDictionary *)screenshotsOnDesktop;
- (NSDictionary *)screenshotsAtPath:(NSString *)dirpath modifiedAfterDate:(NSDate *)lmod;
- (void)checkForScreenshotsAtPath:(NSString *)dirpath;
- (NSDictionary *)findUnprocessedScreenshotsOnDesktop;

Теперь в файле реализации, сначала добавьте этот код:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    screenshotLocation = [[NSString stringWithString:@"~/Desktop"] retain];
    screenshotFilenameSuffix = [[NSString stringWithString:@".png"] retain];
    knownScreenshotsOnDesktop = [[self screenshotsOnDesktop] retain];
    [self startObservingDesktop];
}

Это устанавливает переменные вверх, когда вызывается все методы. Затем добавьте:

- (void)onDirectoryNotification:(NSNotification *)n {
    id obj = [n object];
    if (obj && [obj isKindOfClass:[NSString class]]) {
        [self checkForScreenshotsAtPath:screenshotLocation];
    }
}

- (void)startObservingDesktop {
    if (shouldObserveDesktop)
        return;
    NSDistributedNotificationCenter *dnc = [NSDistributedNotificationCenter defaultCenter];
    [dnc addObserver:self selector:@selector(onDirectoryNotification:) name:@"com.apple.carbon.core.DirectoryNotification" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
    shouldObserveDesktop = YES;
}

- (void)stopObservingDesktop {
    if (!shouldObserveDesktop)
        return;
    NSDistributedNotificationCenter *dnc = [NSDistributedNotificationCenter defaultCenter];
    [dnc removeObserver:self name:@"com.apple.carbon.core.DirectoryNotification" object:nil];
    shouldObserveDesktop = NO;
}

Здесь мы наблюдаем уведомление, которое будет вызываться при снятии снимка экрана и передать ему метод вызова (в данном случае onDirectoryNotification:). Существует также способ прекратить наблюдение за рабочим столом/уведомлением. Уведомление вызывает checkForScreenshotsAtPath:, которое будет проверять скриншоты на рабочем столе. Ниже приведен код этого метода и другие методы, которые он вызывает:

-(void)checkForScreenshotsAtPath:(NSString *)dirpath {        
    NSDictionary *files;
    NSArray *paths;

    // find new screenshots
    if (!(files = [self findUnprocessedScreenshotsOnDesktop]))
        return;

    // sort on key (path)
    paths = [files keysSortedByValueUsingComparator:^(id a, id b) { return [b compare:a]; }];

    // process each file
    for (NSString *path in paths) {
        // Process the file at the path
    }
}

-(NSDictionary *)findUnprocessedScreenshotsOnDesktop {
    NSDictionary *currentFiles;
    NSMutableDictionary *files;
    NSMutableSet *newFilenames;

    currentFiles = [self screenshotsOnDesktop];
    files = nil;

    if ([currentFiles count]) {
        newFilenames = [NSMutableSet setWithArray:[currentFiles allKeys]];
        // filter: remove allready processed screenshots
        [newFilenames minusSet:[NSSet setWithArray:[knownScreenshotsOnDesktop allKeys]]];
        if ([newFilenames count]) {
            files = [NSMutableDictionary dictionaryWithCapacity:1];
            for (NSString *path in newFilenames) {
                [files setObject:[currentFiles objectForKey:path] forKey:path];
            }
        }
    }

    knownScreenshotsOnDesktop = currentFiles;
    return files;
}

-(NSDictionary *)screenshotsOnDesktop {
    NSDate *lmod = [NSDate dateWithTimeIntervalSinceNow:-5]; // max 5 sec old
    return [self screenshotsAtPath:screenshotLocation modifiedAfterDate:lmod];
}

Это были первые 3 метода, которые вызвало уведомление в очереди, а следующий код - это окончательный метод screenshotsAtPath:modifiedAfterDate:, который я буду предупреждать вас, очень длинный, поскольку он должен подтвердить, что файл определенно является скриншотом:

-(NSDictionary *)screenshotsAtPath:(NSString *)dirpath modifiedAfterDate:(NSDate *)lmod {
    NSFileManager *fm = [NSFileManager defaultManager];
    NSArray *direntries;
    NSMutableDictionary *files = [NSMutableDictionary dictionary];
    NSString *path;
    NSDate *mod;
    NSError *error;
    NSDictionary *attrs;

    dirpath = [dirpath stringByExpandingTildeInPath];

    direntries = [fm contentsOfDirectoryAtPath:dirpath error:&error];
    if (!direntries) {
        return nil;
    }

    for (NSString *fn in direntries) {

        // always skip dotfiles
        if ([fn hasPrefix:@"."]) {
            //[log debug:@"%s skipping: filename begins with a dot", _cmd];
            continue;
        }

        // skip any file not ending in screenshotFilenameSuffix (".png" by default)
        if (([fn length] < 10) ||
            // ".png" suffix is expected
            (![fn compare:screenshotFilenameSuffix options:NSCaseInsensitiveSearch range:NSMakeRange([fn length]-5, 4)] != NSOrderedSame)
            )
        {
            continue;
        }

        // build path
        path = [dirpath stringByAppendingPathComponent:fn];

        // Skip any file which name does not contain a space.
        // You want to avoid matching the filename against
        // all possible screenshot file name schemas (must be hundreds), we make the
        // assumption that all language formats have this in common: it contains at least one space.
        if ([fn rangeOfString:@" "].location == NSNotFound) {
            continue;
        }

        // query file attributes (rich stat)
        attrs = [fm attributesOfItemAtPath:path error:&error];
        if (!attrs) {
            continue;
        }

        // must be a regular file
        if ([attrs objectForKey:NSFileType] != NSFileTypeRegular) {
            continue;
        }

        // check last modified date
        mod = [attrs objectForKey:NSFileModificationDate];
        if (lmod && (!mod || [mod compare:lmod] == NSOrderedAscending)) {
            // file is too old
            continue;
        }

        // find key for NSFileExtendedAttributes
        NSString *xattrsKey = nil;
        for (NSString *k in [attrs keyEnumerator]) {
            if ([k isEqualToString:@"NSFileExtendedAttributes"]) {
                xattrsKey = k;
                break;
            }
        }
        if (!xattrsKey) {
            // no xattrs
            continue;
        }
        NSDictionary *xattrs = [attrs objectForKey:xattrsKey];
        if (!xattrs || ![xattrs objectForKey:@"com.apple.metadata:kMDItemIsScreenCapture"]) {
            continue;
        }

        // ok, let use this file
        [files setObject:mod forKey:path];
    }

    return files;
}

Хорошо, там у вас есть. То, как я смог обнаружить, когда пользователь берет скриншот, у него, вероятно, есть несколько ошибок, но в настоящий момент он работает нормально. Если вы хотите, чтобы весь код в нем был здесь, ссылки на него на pastebin.com:

Заголовок - http://pastebin.com/gBAbCBJB

Реализация - http://pastebin.com/VjQ6P3zQ

Ответ 3

вам нужно зарегистрировать объект, чтобы получить системное уведомление, когда пользователь делает снимок экрана

так:

[[NSNotificationCenter defaultCenter] addObserver: theObjectToRecieveTheNotification selector:@selector(theMethodToPerformWhenNotificationIsRecieved) name:@"theNameOftheScreenCapturedNotification" object: optionallyAnObjectOrArgumentThatIsPassedToTheMethodToBecalled];

не уверен, что имя уведомления, но оно, вероятно, там.

Не забудьте также отменить регистрацию в dealloc:

[[NSNotificationCenter defaultCenter] removeObserver: self];