package com.niklim.clicktrace.capture; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Robot; import java.awt.image.BufferedImage; import java.util.LinkedList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.inject.Inject; import com.google.inject.Singleton; import com.niklim.clicktrace.TimeMeter; import com.niklim.clicktrace.capture.voter.LineVoter; import com.niklim.clicktrace.controller.ActiveSession; import com.niklim.clicktrace.model.Click; import com.niklim.clicktrace.props.UserProperties; import com.niklim.clicktrace.service.ImageSaver; import com.niklim.clicktrace.service.ScreenShotUtils; /** * Screenshots capturing manager. {@link UserProperties} provides configuration. */ @Singleton public class CaptureManager { private static final Logger log = LoggerFactory.getLogger(CaptureManager.class); private static final int CAPTURE_PERIOD_MS = 1000; @Inject private Robot robot; @Inject private ChangeDetector detector; @Inject private ActiveSession activeSession; @Inject private UserProperties props; @Inject private ImageSaver imageSaver; private List<Click> clicks = new LinkedList<Click>(); private BufferedImage lastImage; private Timer time; private boolean captureMouseClicks; private Rectangle screenshotRect; /** * Starts periodic screenshot capturing. */ public void start() { log.info("Capturing started"); configure(); time = new Timer(); time.schedule(new CaptureTask(), CAPTURE_PERIOD_MS, CAPTURE_PERIOD_MS); } private void configure() { captureMouseClicks = props.getCaptureMouseClicks(); if (props.getCaptureFullScreen()) { screenshotRect = null; } else { screenshotRect = props.getCaptureRectangle(); } detector.setVoter(new LineVoter(props.getCaptureSensitivity())); } /** * Stops screenshots capturing. */ public void stop() { if (time != null) { log.info("Capturing stopped"); time.cancel(); } time = null; if (lastImage != null) { drawClicks(lastImage); imageSaver.save(lastImage, activeSession.getSession().getName()); lastImage = null; waitForImageSaver(); } } /** * Wait 1 sec for ImageSaver to save all pending images. Brute solution, but * effective. */ private void waitForImageSaver() { try { Thread.sleep(1000); } catch (InterruptedException e) { log.error("", e); } } private class CaptureTask extends TimerTask { @Override public void run() { capture(Optional.<Click> absent()); } } /** * Takes a screenshot and decides whether it should be saved. On screenshot * save it stores recorded mouse clicks on the last screenshot. */ public synchronized void capture(Optional<Click> clickOpt) { TimeMeter tm = TimeMeter.start("CaptureManager.capture", log); Rectangle captureRect = getCaptureRectangle(); TimeMeter tmRobot = TimeMeter.start("CaptureManager.robot", log); BufferedImage image = robot.createScreenCapture(captureRect); tmRobot.stop(); if (detector.detect(lastImage, image)) { log.debug("Screen change detected"); if (clickOpt.isPresent()) { clicks.add(clickOpt.get()); } if (lastImage != null) { drawClicks(lastImage); imageSaver.save(lastImage, activeSession.getSession().getName()); } lastImage = image; } else if (clickOpt.isPresent()) { clicks.add(clickOpt.get()); } tm.stop(); } private void drawClicks(BufferedImage image) { if (captureMouseClicks) { ScreenShotUtils.markClicks(image, clicks); } clicks.clear(); } private Rectangle getCaptureRectangle() { if (screenshotRect != null) { return screenshotRect; } else { Dimension screenSize = ScreenUtils.getPrimarySize(); return new Rectangle(0, 0, screenSize.width, screenSize.height); } } }