Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 755e63639f6486a6bdd71564aa4239ec552f580d @popoffka committed Feb 5, 2012
Showing with 510 additions and 0 deletions.
  1. +7 −0 AlarmistExceptions.py
  2. +10 −0 BasePanic.py
  3. +48 −0 BaseSensor.py
  4. +19 −0 BaseUI.py
  5. +16 −0 CMDPanic.py
  6. +30 −0 ConsoleUI.py
  7. +19 −0 LICENSE
  8. +33 −0 PeriodicTimer.py
  9. +53 −0 README.markdown
  10. +42 −0 ThinkPadShockSensor.py
  11. +144 −0 WXTaskbarUI.py
  12. +66 −0 alarmist.py
  13. +23 −0 config.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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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 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.
@@ -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()
@@ -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)
@@ -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
Oops, something went wrong.

0 comments on commit 755e636

Please sign in to comment.