#!/usr/bin/perl # tp-theft v0.1 (http://thinkwiki.org/wiki/Script_for_theft_alarm_using_HDAPS) # This script uses the HDAPS accelerometer found on recent ThinkPad models # to emit an audio alarm when the laptop is tilted. In sufficiently # populated environments, it can be used as a laptop theft deterrent. # # This file is placed in the public domain and may be freely distributed. use strict; use warnings; use Time::HiRes qw ( alarm ); ############################## # Siren volume and content # Audio volume (0..100) my $volume = 90; # Synthesize a siren: my $alarm_duration = 1.0; #my $play_cmd = "sox -n -q -t ossdsp /dev/dsp synth $alarm_duration sine". # " 2000-4000 sine 4000-2000"; # Play a file: my $play_cmd = "aplay /usr/local/share/sounds/alarm.wav"; ############################## # Other tweakables my $thresh = 0.10; # tilt threshold (increase value to decrease sensitivity) my $interval = 0.1; # sampling interval in seconds my $depth = 10; # number of recent samples to analyze my $pos_file='/sys/devices/platform/hdaps/position'; my $light_file = '/proc/acpi/ibm/light'; my $lid_file = '/proc/acpi/button/lid/LID/state'; my $verbose = 1; my $use_light = 1; # 0=off, 1=on ############################## # ThinkLight blinking sub set_light { my ($state) = @_; return if (!$use_light && $state eq 'off'); unless (open(LGH, "+<$light_file")) { print "Cannot open $light_file, disabling ThinkLight indicator: $!\n"; $use_light = 0; return; } my $light = ; if ($state eq 'on' && $light =~ m/status:\s*on$/) { print "Disabling tl\n"; $use_light = '0'; close(LGH); return; } if (!$use_light && $state eq 'on' && $light =~ m/status:\s*off$/) { $use_light = '1'; } alarm($alarm_duration/2) if ($state eq 'on'); print LGH (check_lid() eq 'open' && $state)."\n"; close(LGH); } sub light_restore { return unless $use_light; open(LGH, ">$light_file") or die "Cannot open $light_file: $!\n"; print LGH "off\n"; close(LGH); } ############################## # Lid checking sub check_lid { open(LID, "<$lid_file") or return; my $lid = ; return ($lid =~ m/state: *open$/) ? 'open' : 'closed'; } ############################## # Code sub get_pos { open(POS,"<",$pos_file) or die "Can't open HDAPS file $pos_file: $!\n"; $_=; m/^\((-?\d+),(-?\d+)\)$/ or die "Can't parse $pos_file content\n"; return ($1,$2); } sub stddev { my $sum=0; my $sumsq=0; my $n=$#_+1; for my $v (@_) { $sum += $v; $sumsq += $v*$v; } return sqrt($n*$sumsq - $sum*$sum)/($n*($n-1)); } my (@XHIST, @YHIST); my ($x,$y) = get_pos; for (1..$depth) { push(@XHIST,$x); push(@YHIST,$y); } my $alarm_file; # flags ongoing alarm (and stores saved mixer settings) $SIG{ALRM} = sub { set_light('off'); }; $SIG{INT} = $SIG{HUP} = $SIG{TERM} = sub { light_restore() if $use_light; exit 0; }; while (1) { my ($x,$y) = get_pos; shift(@XHIST); push(@XHIST,$x); shift(@YHIST); push(@YHIST,$y); my $xdev = stddev(@XHIST); my $ydev = stddev(@YHIST); # Print variance and history print "X: v=$xdev (".join(',',@XHIST).") Y: v=$ydev (".join(",",@YHIST). ")\n" if $verbose>1; my $tilted = $xdev>$thresh || $ydev>$thresh; if ($tilted && !(defined($alarm_file) && -f $alarm_file)) { print "ALARM\n" if $verbose>0; $alarm_file = `mktemp /tmp/hdaps-tilt.XXXXXXXX` or die "mktemp: $?"; chomp($alarm_file); set_light('on'); system('/bin/bash', '-c', <<"EOF")==0 or die "Failed: $?"; ( trap \"alsactl -f $alarm_file restore; rm -f $alarm_file;" EXIT HUP QUIT TERM alsactl -f $alarm_file store && amixer -q set Master $volume% unmute && $play_cmd) & EOF } select(undef, undef, undef, $interval); # sleep }