#!/usr/bin/perl -w

# screenspy - Linux desktop monitoring daemon. Must be run as root.

# This script monitors one or more hardware devices (usually under /dev) for
# Linux systems running an X window manager.
#
# This script must be run as root because usually only root can read from
# hardware devices directly. You can set up a panel launcher to run this
# script with `sudo` if you previously set up the sudoers file to let your
# user run this script with no password.
#
# --Kirsle
# http://sh.kirsle.net/

use strict;
use warnings;
use threads;
use threads::shared;

#################
# Configuration #
#################

# X display to grab screenshots from.
$ENV{DISPLAY} ||= ":0.0";

# Devices to monitor.
our @devices = (
	"/dev/console",    # keyboard input
	"/dev/input/mice", # mouse input
);

# Screenshot command.
our $screenshot = 'scrot -q 85 "%s"';

# Output directory for screenshots.
our $outdir = '/home/kirsle/Pictures/screenspy';

# Calibration: number of seconds (after no activity) for it to take a screenshot.
our $threshold = 2; # I found that 2 seconds is the best for my system, 1 second and it takes screenshots too often.

# Calibration: if there's too much activity after the threshold, take screenshots every N seconds.
our $prolonged = 5;

# If you want some indication that the app is running, put the command to run in here.
# This command will be repeatedly run. Recommended is to use zenity and put an icon in
# your system tray. Leave blank for no command.
our $notify = "zenity --notification --window-icon=/usr/share/pixmaps/gnome-spider.png --text 'Running...'";

#####################
# End Configuration #
#####################

# Only let this script run once.
&checkdupes();

# Each thread will report the time when the last event happened.
my %lastEvent : shared;
my %changed : shared;

# Spawn a thread for each device.
my @threads = ();
foreach my $dev (@devices) {
	# Make sure we can read the device.
	if (!-r $dev) {
		system("zenity --error --text \"Don't have permission to read from device $dev\"");
	}
	push (@threads, threads->create (\&monitor, $dev));
}

# If they want a command run, spawn a thread for it too.
if (length $notify) {
	push (@threads, threads->create (\&notify));
}

# Loop forever and let the threads do their thing.
while (1) {
	# See if any events have stopped for longer than the threshold.
	foreach my $dev (keys %lastEvent) {
		if ($lastEvent{$dev} > 0 && time() - $lastEvent{$dev} >= $threshold) {
			# take screenshot
			print "$dev: idle, taking screenshot (" . (time() - $lastEvent{$dev}) . "; $threshold)\n";
			&screenshot();
			$lastEvent{$dev} = 0;
			$changed{$dev} = 0;
		}
	}
	select(undef,undef,undef,0.01);
}

sub monitor {
	my $device = shift;
	print "Monitoring device: $device\n";

	# Store the time when the last event was seen.
	$lastEvent{$device} = 0;

	# When the lastEvent is first set (away from 0), record the time it was changed.
	# This way for prolonged activity we can still take screenshots.
	$changed{$device} = 0;

	open (READ, $device);
	my $buffer;
	while (read(READ, $buffer, 1)) {
		# Store the last event time
		if ($lastEvent{$device} == 0) {
			$changed{$device} = time();
		}
		$lastEvent{$device} = time();

		# Too much activity?
		if ($changed{$device} > 0 && time() - $changed{$device} > $prolonged) {
			# Take screenshot.
			print "$device: prolonged activity (> $prolonged seconds)\n";
			&screenshot();
			$changed{$device} = time();
		}
	}
	close (READ);
}

sub notify {
	while (1) {
		system($notify);
	}
}

sub screenshot {
	print "Taking screenshot!\n";

	my @time = localtime(time());
	my $date = join(" ",
		join("-",
			sprintf("%04d", $time[5] + 1900),
			sprintf("%02d", $time[4] + 1),
			sprintf("%02d", $time[3]),
		),
		join(":",
			sprintf("%02d", $time[2]),
			sprintf("%02d", $time[1]),
			sprintf("%02d", $time[0]),
		),
	);

	my $fname = $date;
	my $i = 1;
	while (-f "$outdir/$fname.png") {
		$fname = $date . " [$i]";
		$i++;
	}
	my $cmd = $screenshot;
	$cmd =~ s/%s/$outdir\/$fname\.png/ig;
	system($cmd);
}

sub checkdupes {
	my $ps = `ps aux | grep screenspy`;
	foreach my $line (split(/\n/,$ps)) {
		chomp $line;
		next if $line =~ /grep/i;
		next if $line =~ /$$/i;
		if ($line) {
			die "Script is already running!\n";
		}
	}
}