Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
popoffka committed Feb 5, 2012
0 parents commit 755e636
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 0 deletions.
7 changes: 7 additions & 0 deletions AlarmistExceptions.py
@@ -0,0 +1,7 @@
class WrongOptionsError(Exception):
def __init__(self, module):
self.module = module

class NotSupportedError(Exception):
def __init__(self, module):
self.module = module
10 changes: 10 additions & 0 deletions BasePanic.py
@@ -0,0 +1,10 @@
class BasePanic():
def __init__(self, options):
# well, do whatever you want to with options
# you can do something relatively slow here (like 1-2 secs), because the Panick object is created when the app is started,
# not when it's actually time to panic
pass

def panic(self):
# we're screwed, panic!
pass
48 changes: 48 additions & 0 deletions BaseSensor.py
@@ -0,0 +1,48 @@
from PeriodicTimer import PeriodicTimer

class BaseSensor():
def __init__(self, options, panicCallback, stopPanicCallback):
# you should probably save the callbacks and do something with the options
# if options are wrong, raise WrongOptionsError
# if the sensor is not suported (i.e. a required dll isn't installed), raise NotSupportedError
self.delay = 1
self.timer = None
self.panicking = False
self.panicCallback = panicCallback
self.stopPanicCallback = stopPanicCallback

def checkIfWeAreScrewed(self):
# this function is NOT required and will not be called by the program itself
# however, functions start() and stop() below use it, so if you're too lazy to write your own start() and stop(),
# you can simply redefine this function
return False

def handleData(self):
# same as above, basically
res = self.checkIfWeAreScrewed()
if res and (not self.panicking):
self.panicking = True
self.panicCallback()
elif (not res) and self.panicking:
self.panicking = False
self.stopPanicCallback()

def start(self):
# start periodic checks
if self.timer:
return

self.timer = PeriodicTimer(self.delay, self.handleData)
self.timer.start()

def stop(self):
# stop periodic checks
if not self.timer:
return

self.timer.cancel()
self.timer = None

def close(self):
# close all the handles and shit
pass
19 changes: 19 additions & 0 deletions BaseUI.py
@@ -0,0 +1,19 @@
class BaseUI():
def __init__(self, options, toggleCallback, exitCallback, initState):
# you should save the toggleCallback and initState, I guess
self.toggleCallback = toggleCallback
self.exitCallback = exitCallback
self.state = initState

def changeState(self, state):
# 0 means unarmed
# 1 means armed
pass

def changePanicState(self, state):
# 0 < x < 1 means panicking and part x of the timeout has passed
pass

def close(self):
# if you have to destroy anything, feel free to do it right now
pass
16 changes: 16 additions & 0 deletions CMDPanic.py
@@ -0,0 +1,16 @@
from BasePanic import BasePanic
from threading import Thread
from subprocess import Popen

class CMDPanic(BasePanic):
"""
Launches the specified application using subprocess.Popen.
Options should either be a string or an iterable object with strings
(i.e., 'shutdown /h' or ['shutdown', '/h'])
Should work under any OS.
"""
def __init__(self, options):
self.command = options

def panic(self):
Popen(self.command)
30 changes: 30 additions & 0 deletions ConsoleUI.py
@@ -0,0 +1,30 @@
from BaseUI import BaseUI
from threading import Thread

class ConsoleUI(BaseUI):
'''
A very simple console-based UI for the Alarmist.
Not really intended for actual use, more of a development tool (although it seems to be functional
and you're free to try to use it).
Doesn't have any options.
Seems to work under any OS.
'''
def monitorInput(self):
while (True):
command = raw_input()
if command.strip() == 'toggle':
self.toggleCallback()
elif command.strip() == 'exit':
self.exitCallback()

def __init__(self, options, toggleCalback, exitCallback, initState):
BaseUI.__init__(self, options, toggleCalback, exitCallback, initState)
print 'ConsoleUI: state is ', self.state
t = Thread(group=None, target=self.monitorInput, name=None)
t.start()

def changeState(self, state):
print 'ConsoleUI: state changed to',state

def changePanicState(self, state):
print 'ConsoleUI: panic at',state
19 changes: 19 additions & 0 deletions LICENSE
@@ -0,0 +1,19 @@
Copyright (C) 2012 Aleksejs Popovs

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
33 changes: 33 additions & 0 deletions PeriodicTimer.py
@@ -0,0 +1,33 @@
from threading import Thread, Event
class PeriodicTimer(Thread):
"""Call a function every /interval/ seconds:
t = PeriodicTimer(30.0, f, args=[], kwargs={})
t.start()
t.cancel()
"""

def __init__(self, interval, function, limit=None, finishCallback=None, args=[], kwargs={}):
Thread.__init__(self)
self.interval = interval
self.function = function
self.limit = limit
self.finishCallback = finishCallback
self.args = args
self.kwargs = kwargs
self.finished = Event()

def cancel(self):
"""Stop the timer if it hasn't finished yet"""
self.finished.set()

def run(self):
i = 0
while (not self.finished.is_set()) and (not self.limit or i < self.limit):
self.finished.wait(self.interval)
if not self.finished.is_set():
self.function(*self.args, **self.kwargs)
i += 1

if not self.finished.is_set() and self.limit and self.finishCallback:
self.finishCallback()
53 changes: 53 additions & 0 deletions README.markdown
@@ -0,0 +1,53 @@
About Alarmist
==============

Alarmist is a piece of software for paranoids just like me. In short, Alarmist periodically asks *sensors* if something wrong is going on and, if the sensors respond positively, initiates a *panic*.

An example usage is the default ThinkPad Shock Sensor and the default hibernation panic. When combined, those will make your ThinkPad hibernate whenever it's moving for a long enough time (i.e. if someone's lifted it up and is trying to steal it). If you use system encryption, then this is a wonderful way to solve one of the most serious problems of system encryption: the danger of your laptop being stolen while it's turned on. There's a [video on YouTube](http://www.youtube.com/watch?v=l0SKIhsyJqQ) demonstrating this example.

Details on inner workings
=========================

Alarmist itself relies on three modules to work:

- the *sensor* is responsible for notifying Alarmist whenever something weird is going on
- - **ThinkPadShockSensor** is the only included sensor at the moment. It checks if your laptop is moving. Unfortunately, it only works on [ThinkPad laptops with accelerometers](http://www.thinkwiki.org/wiki/Active_Protection_System) and only supports 32-bit Python on Windows (although it is possible to port it to Linux)
- the *panic manager* is responsible for quickly doing whatever you might want it to do whenever Alarmist tells it someting's wrong
- - the only included panick manager is **CMDPanic**, which runs a specified application during panic events. CMDPanic supports any OS.
- the *user interface* is responsible for notifying you about what's going on and allowing you to temporarily turn Alarmist off
- - one of the included UIs is **WXTaskbarUI**, which creates a nice icon in your taskbar and allows you to toggle Alarmist state by either double-clicking the icon or using the system-wide hotkey (which is Alt-F1 by default, but you're free to change it). As the name implies, WXTaskbarUI uses wxPython and therefore requires wxPython to be installed. Even though I didn't try to use WXTaskbarUI under any OS other than Windows, I believe it will work under any OS.
- - the other is **ConsoleUI**. ConsoleUI is mainly for development purposes, although you're free to try it if you can't/don't want to use WXTaskbarUI for some reason.

More info on each module is available in it's corresponding .py file (e.g. ThinkPadShockSensor.py)

Configuration and usage
=======================

Configuration is done via a file named `config.py`. It should be pretty straightforward to modify and there are comments near most of the fields, so I'll assume there's no need to discuss configuration.

Launching Alarmist is as easy as simply typing

cd ./alarmist
python ./alarmist.py

in your command prompt. However, Windows users (and maybe some other OS users too) might want to use `pythonw` instead of `python`, because otherwise they'll get a practically useless command prompt window (unless you use ConsoleUI, of course).

Everything else, obviously, depends on the UI you are using. WXTaskbarUI should pretty much straightforward:

![WXTaskbarUI screenshot](http://s1.hostingkartinok.com/uploads/images/2012/02/34d517876f0a56f91525a97d0ab64484.png)

The big rectangle on the left display the current state of Alarmist (red means unarmed, green means armed). The smaller rectangle on the right is a 'panic meter' that fills with red when the sensor is reporting danger (the speed at which it fills up is defined by the timeout variable in `config.py`). Double-clicking the icon or using the hotkey you defined in `config.py` will toggle the state of Alarmist, right-clicking the icon will open the menu that allows you to either toggle the state of Alarmist (again!) or exit Alarmist.

Contributing to Alarmist
========================

If you wish to help me add features to Alarmist (for example, I'd love to see some new interesting sensors) or make the existing features better (e.g. port ThinkPadShockSensor to Linux), you may send me pull requests. If you have any questions regarding how something works or what's the best way to implement your idea, [contact me](http://popoffka.ru) and I'll be glad to help you.

By the way, Alarmist is licensed under MIT License, see LICENSE for more info.

Donations
=========

If you really really like Alarmist, feel free to donate. Or not to donate.

[![PayPal - The safer, easier way to pay online!](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=TZWCNZQ6N9KYJ)
42 changes: 42 additions & 0 deletions ThinkPadShockSensor.py
@@ -0,0 +1,42 @@
import ctypes
from BaseSensor import BaseSensor
from AlarmistExceptions import WrongOptionsError, NotSupportedError

class SensorData(ctypes.Structure):
_fields_ = [
('status', ctypes.c_int),
('raw_x', ctypes.c_short),
('raw_y', ctypes.c_short),
('x', ctypes.c_short),
('y', ctypes.c_short),
('temp', ctypes.c_byte),
('center_x', ctypes.c_short),
('center_y', ctypes.c_short),
]

class ThinkPadShockSensor(BaseSensor):
"""
An Alarmist sensor that uses Sensor.dll to get data from your ThinkPad's aceelerometer (APS).
Will throw NotSupportedError if you don't have a ThinkPad or Sensor.dll isn't installed.
Options[0] is the panic threshold (when your TP's unstability reaches it, the sensor panics). On my x121e, unstability is between 0 and 9
and I find 4 to be the ideal threshold. Options[1] is the delay between checks.
Only works on Windows and under 32-bit versions of Python.
"""
def __init__(self, options, panicCallback, stopPanicCallback):
BaseSensor.__init__(self, options, panicCallback, stopPanicCallback)

try:
self.panicThreshold = options[0]
self.delay = options[1]
except:
raise WrongOptionsError('ThinkPadShockSensor')

try:
self.func = ctypes.windll.Sensor.ShockproofGetAccelerometerData
except:
raise NotSupportedError('ThinkPadShockSensor')

def checkIfWeAreScrewed(self):
data = SensorData()
self.func(ctypes.byref(data))
return data.status >= self.panicThreshold

0 comments on commit 755e636

Please sign in to comment.