8

To analyze my own computer usage, not to spy on anyone (although it crossed my mind), I want to have cron capture the current screen every minute.

 * * * * * /bin/bash -c "/usr/sbin/screencapture /somedir/screen.png"

in the crontab will execute and take a screen capture. However, it is totally black because it is not running as, well, me. Any idea how to allow a cron job to capture my screen?

Update: I added a say whoami to the same cron command and it confirms it is running as my user (no sudo or other users involved). I access the crontab from a terminal as myself.

So, it runs as me, but is not attached to my windowing system. Any ideas?

1
  • Are you adding this to your system (root) crontab, or your user crontab? Aug 21, 2009 at 4:49

4 Answers 4

10

If you look at the end of the screencapture manpage, you see it says:

To capture screen content while logged in via ssh, you must launch
screencapture in the same mach bootstrap hierarchy as loginwindow:

PID=pid of loginwindow
sudo launchctl bsexec $PID screencapture [options]

So I think you could do somethig like this in your shell script that cron calls:

#/bin/sh
loginwindowpid=`ps axo pid,comm | grep '[l]oginwindow' | sed -n 's# *\([^ ]*\).*$#\1#p'`
sudo launchctl bsexec $loginwindowpid screencapture /somedir/screen.png

Of course, you'll need your userid set to not need a password for sudo.
That is you'd set in /etc/sudoers with the visudo command

youruserid     ALL=(ALL) NOPASSWD: ALL
3
  • Wonderful solution to my question. Very complete. I had read the man page but same mach bootstrap hierarchy went right over my head so I headed straight to SU to help make sense of it. Thank you very much. Aug 22, 2009 at 21:03
  • 1
    You can eliminate the call to grep -v by making the first call look like grep [l]oginwindow and might be able to eliminate the call to cut by using ps axo comm Aug 25, 2009 at 18:55
  • Very nice solution. Dec 15, 2009 at 13:39
1

If you don't want to make your user account have sudo privileges (because you worry about security), you can use applescript to solve this. In my case I am using screencapture indirectly as part of another program so I'm converting that to an example here to use screencapture directly as the OP did.

Here's a daily cron execution at 8:11 AM:

11 8 * * * /opt/myprogram/daily_cron.sh > daily_cron_output.txt 2>&1'

daily_cron.sh:

osascript -e 'tell app "Terminal"
    do script "/usr/sbin/screencapture /somedir/screen.png > daily_output.txt 2>&1 && exit"
end tell'

You can leave out the > daily_output.txt 2>&1 bit if you don't expect any output from the program you call (like screencapture). The && exit is on there to close the Terminal window when it's done. If your Terminal doesn't close when you run exit, check the Preferences->Settings->Profiles->Shell and check the When the shell exits setting.

Terminal shell exit setting

The first time you execute the program via cron it may popup a window asking you for permission, which you must accept. So run the cron job the first time while you're sitting there.

1
  • 1
    @BlindSpots Thank you for the edit and suggestion! Jan 19 at 17:54
0

If you are going to use sudo to set up cron for another account, you can use the -u switch along with sudo.

example:

sudo -u Your_user crontab -e

else just log into that account and use crontab -e.

0

As an aside, I use Java for the same job. It starts on boot and I look through the images at the end of the day.

/**
 * Code modified from code given in http://whileonefork.blogspot.co.uk/2011/02/java-multi-monitor-screenshots.html following a SE question at  
 * http://stackoverflow.com/questions/10042086/screen-capture-in-java-not-capturing-whole-screen and then modified by a code review at http://codereview.stackexchange.com/questions/10783/java-screengrab
 */
package com.tmc.personal;

import java.awt.AWTException;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

class ScreenCapture {

    static int minsBetweenScreenshots = 5;

    public static void main(String args[]) {
        int indexOfPicture = 1000;// should be only used for naming file...
        while (true) {
            takeScreenshot("ScreenCapture" + indexOfPicture++);
            try {
                TimeUnit.MINUTES.sleep(minsBetweenScreenshots);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //from http://www.coderanch.com/t/409980/java/java/append-file-timestamp
    private  final static String getDateTime()
    {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd_hh:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("PST"));
        return df.format(new Date());
    }

    public static void takeScreenshot(String filename) {
        Rectangle allScreenBounds = getAllScreenBounds();
        Robot robot;
        try {
            robot = new Robot();
            BufferedImage screenShot = robot.createScreenCapture(allScreenBounds);
            ImageIO.write(screenShot, "jpg", new File(filename + getDateTime()+ ".jpg"));
        } catch (AWTException e) {
            System.err.println("Something went wrong starting the robot");
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("Something went wrong writing files");
            e.printStackTrace();
        }
    }

    /**
     * Okay so all we have to do here is find the screen with the lowest x, the
     * screen with the lowest y, the screen with the higtest value of X+ width
     * and the screen with the highest value of Y+height
     * 
     * @return A rectangle that covers the all screens that might be nearby...
     */
    private static Rectangle getAllScreenBounds() {
        Rectangle allScreenBounds = new Rectangle();
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = ge.getScreenDevices();

        int farx = 0;
        int fary = 0;
        for (GraphicsDevice screen : screens) {
            Rectangle screenBounds = screen.getDefaultConfiguration().getBounds();
            // finding the one corner
            if (allScreenBounds.x > screenBounds.x) {
                allScreenBounds.x = screenBounds.x;
            }
            if (allScreenBounds.y > screenBounds.y) {
                allScreenBounds.y = screenBounds.y;
            }
            // finding the other corner
            if (farx < (screenBounds.x + screenBounds.width)) {
                farx = screenBounds.x + screenBounds.width;
            }
            if (fary < (screenBounds.y + screenBounds.height)) {
                fary = screenBounds.y + screenBounds.height;
            }
            allScreenBounds.width = farx - allScreenBounds.x;
            allScreenBounds.height = fary - allScreenBounds.y;
        }
        return allScreenBounds;
    }
}
4
  • Major syntax errors with what you posted
    – bafromca
    Dec 18, 2012 at 20:18
  • apologies - I've added the missing brackets...
    – Joe
    Dec 18, 2012 at 20:51
  • It's still missing one more curly brace at the end. It open class, main(), while, try/catch and then closes try/catch, while, main() but it doesn't close the class. And then it's missing its import statements at the top too. This is what I ended up with just to get it to compile. It still doesn't run: pastebin.com/PrSfhUkS
    – bafromca
    Dec 19, 2012 at 17:21
  • Okay - I've given the full code (also avaible at codereview.stackexchange.com/questions/10783/java-screengrab ) - can I ask you to fix the formatting? - I've got to rush off...
    – Joe
    Dec 19, 2012 at 21:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .