#!/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 (\¬ify)); } # 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"; } } }