Detect when a User takes a Screenshot

后端 未结 3 1202
死守一世寂寞
死守一世寂寞 2021-01-30 15:26

I am looking for way for my app to receive a notification when the user takes a screenshot either with Command-Shift-3 or Command-Shift-4.

An e

相关标签:
3条回答
  • 2021-01-30 15:46

    youll have to register an object to recieve the system notification when a user takes a screen shot

    so:

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

    not sure what the notification name is but it's probably out there.

    don't forget to unregister yourself as well in dealloc:

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    0 讨论(0)
  • 2021-01-30 15:51

    This is how I have done it, it is a bit complicated but I will try and take you through it step by step:


    Before we start, in your header file declare the following variables and methods :

    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;
    

    Now in your implementation file, firstly add this code:

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

    This sets up the variables up for when all the methods are called. Next add:

    - (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;
    }
    

    Here we observe the notification that will be called when a screenshot is taken and pass it the method to call (in this case onDirectoryNotification:). There is also the method to stop observing the desktop/notification. The notification calls checkForScreenshotsAtPath: which will check for screenshots on the desktop. The following is the code for that method and the other methods that it calls:

    -(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];
    }
    

    They were the first 3 methods that the notification in turn calls and the following code is the final method screenshotsAtPath:modifiedAfterDate: which I will warn you is extremely long as it has to confirm that the file is definitely a screenshot:

    -(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's use this file
            [files setObject:mod forKey:path];
        }
    
        return files;
    }
    

    Well, there you have it. That's how I was able to detect when the user takes a screenshot, it probably has a few bugs but it seems to work fine at the moment. If you want all the code in one here are the links for it at pastebin.com:

    Header - http://pastebin.com/gBAbCBJB

    Implementation - http://pastebin.com/VjQ6P3zQ

    0 讨论(0)
  • 2021-01-30 15:58

    This was mentioned in one of the earlier comments, but you can use an NSMetadataQuery searching for files where kMDItemIsScreenCapture = 1. This is a special attribute that gets added to screenshot files.

    I just whipped up a little demo showing how to do this and posted it on github:

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

    0 讨论(0)
提交回复
热议问题