summaryrefslogtreecommitdiff
path: root/lyricstimer
diff options
context:
space:
mode:
authorTim Redfern <tim@eclectronics.org>2013-11-18 14:40:41 +0000
committerTim Redfern <tim@eclectronics.org>2013-11-18 14:40:41 +0000
commita36735b4585521218268da5fed2214ba239b4c2a (patch)
tree77f951acb62d61c88b0b01d4bcb34367e44abc91 /lyricstimer
parent6c5ba0ff0a4301b932c70aee6b4c87b2092d8bff (diff)
simple lyrics script
Diffstat (limited to 'lyricstimer')
-rw-r--r--lyricstimer/keyview.py295
-rw-r--r--lyricstimer/pyxhook.py359
-rwxr-xr-xlyricstimer/timelyrics60
3 files changed, 714 insertions, 0 deletions
diff --git a/lyricstimer/keyview.py b/lyricstimer/keyview.py
new file mode 100644
index 0000000..ea4c3f3
--- /dev/null
+++ b/lyricstimer/keyview.py
@@ -0,0 +1,295 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from xml.sax.saxutils import escape, quoteattr
+import gtk, gtk.glade
+from time import time
+
+import pyxhook
+
+# Want to handle/show these in a nice emacsy way
+MODIFIERS = (
+ 'Control_L',
+ 'Control_R',
+ 'Alt_L',
+ 'Alt_R',
+ 'Super_L',
+ 'Mode_switch'
+)
+
+CHORD_PREFIXES = (
+ 'f1-',
+ 'f2-'
+)
+
+SHIFT_KEYS = (
+ 'Shift_L',
+ 'Shift_R',
+)
+
+# Alter the appearance of some key events
+KEY_MAP = {
+ 'F1':'f1-',
+ 'F2':'f2-',
+ 'F3':'f3',
+ 'F4':'f4',
+ 'F5':'f5',
+ 'F6':'f6',
+ 'F7':'f7',
+ 'F8':'f8',
+ 'F9':'f9',
+ 'F10':'f10',
+ 'F11':'f11',
+ 'F12':'f12',
+ 'Return':'↲',
+ 'Right': '→',
+ 'Left': '←',
+ 'Up': '↑',
+ 'Down': '↓',
+ 'Control_L':'Ctrl-',
+ 'Control_R':'Ctrl-',
+ 'Mode_switch':'Mod4-',
+ 'Alt_L':'Alt-',
+ 'Alt_R':'Alt-',
+ 'Shift_L':'⇪-',
+ 'Shift_R':'⇪-',
+ 'space': ' ',
+ 'parenleft': '(',
+ 'parenright': ')',
+ 'bracketleft': '[',
+ 'bracketright': ']',
+ 'braceleft': '{',
+ 'braceright': '}',
+ 'BackSpace': '⇤',
+ 'Delete': 'DEL',
+ 'Tab': '↹',
+ 'bar': '|',
+ 'minus': '-',
+ 'plus': '+',
+ 'asterisk': '*',
+ 'equal': '=',
+ 'less': '<',
+ 'greater': '>',
+ 'semicolon': ';',
+ 'colon': ':',
+ 'comma': ',',
+ 'apostrophe': "'",
+ 'quotedbl' : '"',
+ 'underscore' : '_',
+ 'numbersign' : '#',
+ 'percent' : '%',
+ 'exclam' : '!',
+ 'period' : '.',
+ 'slash' : '/',
+ 'backslash' : '\\',
+ 'question' : '?',
+ 'adiaeresis': 'ä',
+ 'odiaeresis': 'ö',
+ 'udiaeresis': 'ü',
+ 'ssharp': 'ß',
+ 'ampersand': '&',
+ 'section': '§',
+}
+
+def get_hook_manager():
+ hm = pyxhook.HookManager()
+ hm.HookKeyboard()
+ hm.HookMouse()
+ #hm.KeyDown = hm.printevent
+ #hm.KeyUp = self.hook_manager_event #hm.printevent
+ #hm.MouseAllButtonsDown = hm.printevent
+ #hm.MouseAllButtonsUp = hm.printevent
+ hm.start()
+ return hm
+
+
+class GTKKeyView:
+ def __init__(self, hm):
+
+ xml = gtk.glade.XML('keyview.libglade')
+ self.window = xml.get_widget('window1')
+ self.window.connect('destroy', self.quit)
+ self.key_strokes = xml.get_widget('label1')
+ self.key_strokes.set_alignment(0, 0)
+ self.menu = xml.get_widget('config-menu')
+ self.font_dialog = xml.get_widget('fontselectiondialog1')
+ self.font = 'Courier Bold 30' #None # text of font description from selection dialog
+ self.init_menu()
+ self.pressed_modifiers = {} # keep track of whats pushed
+ self.hm = hm
+ self.active = True
+ hm.KeyDown = self.hook_manager_down_event
+ hm.KeyUp = self.hook_manager_up_event
+ xml.signal_autoconnect(self)
+ self.max_lines = 3
+ self.show_backspace = True
+ self.show_shift = False
+ self.keys = [] #stack of keys typed
+ self.last_key = 0 # keep track of time
+
+ def init_menu(self):
+ self.font_item = gtk.MenuItem('set font...')
+ self.font_item.connect('activate', self.font_select)
+
+ self.on_item = gtk.CheckMenuItem('listening')
+ self.on_item.set_active(True)
+ self.on_item.connect('activate', self.toggle_active)
+
+ self.quit_item = gtk.MenuItem('quit')
+ self.quit_item.connect('activate', self.quit)
+
+ for item in [self.font_item, self.on_item, self.quit_item]:
+ self.menu.append(item)
+ item.show()
+
+ def font_select(self, widget):
+ response = self.font_dialog.run()
+ self.font_dialog.hide()
+ self.font = self.font_dialog.get_font_name()
+ cur_text = self.key_strokes.get_text()
+ self.update_text(cur_text, self.font)
+
+
+ def toggle_active(self, widget):
+ # active is state after click
+ self.active = widget.get_active()
+
+
+ def quit(self, widget):
+ self.hm.cancel()
+ gtk.main_quit()
+
+
+ def on_eventbox1_popup_menu(self, *args):
+ self.menu.show()
+
+ def on_eventbox1_button_press_event(self, widget, event):
+ if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
+ self.menu.popup(None, None, None, event.button, event.get_time())
+
+ def update_text(self, text, font_desc=None):
+ """
+ see
+ http://www.pygtk.org/docs/pygtk/class-gtklabel.html
+ and
+ http://www.pygtk.org/docs/pygtk/pango-markup-language.html
+ """
+ if font_desc:
+ font_desc_text = 'font_desc=%s' % quoteattr(font_desc)
+ else:
+ font_desc_text = ''
+ pango_markup = """<span %s>%s</span>""" % (font_desc_text, escape(text))
+ self.key_strokes.set_markup(pango_markup)
+
+ def hook_manager_up_event(self, event):
+ if event.Key in self.pressed_modifiers:
+ del self.pressed_modifiers[event.Key]
+
+ def hook_manager_down_event(self, event):
+ #hm.printevent(event)
+ if self.active:
+ e_key = event.Key
+
+ # hack to deal with ctr modifiers in emacsy way
+ modifiers = []
+ postfix = ''
+ for modifier in self.pressed_modifiers.keys():
+ mod = KEY_MAP.get(modifier, modifier)
+ if self.keys and not self.keys[-1].text == mod:
+ modifiers.append(mod)
+ postfix = ' '
+
+ if e_key in MODIFIERS:
+ self.pressed_modifiers[e_key] = 1
+ elif e_key == 'BackSpace' and not self.show_backspace:
+ if self.keys:
+ self.keys.pop()
+ elif e_key in SHIFT_KEYS:
+ if self.show_shift:
+ self.pressed_modifiers[e_key] = 1
+ else:
+ txt = KEY_MAP.get(e_key, e_key)
+
+ prefix = ''.join(modifiers)
+ txt = Text(txt, prefix, postfix)
+
+ isseq = (
+ len(self.keys) > 0 and
+ (self.keys[-1].is_chord_prefix
+ or
+ self.keys[-1].is_char and time() - self.last_key < 1)
+ )
+
+ if not (txt.is_char and isseq):
+ self.keys.append(Text("\n"))
+
+ self.keys.append(txt)
+
+ self.last_key = time()
+
+
+ # limit line lengths
+ self.keys = limit_text(self.keys, self.max_lines)
+ new_text = ''.join([repr(x) for x in self.keys])
+
+ self.update_text(new_text, self.font)
+
+def limit_text(text_list, max_lines):
+ r"""
+ >>> lines = [Text('\n')]*4
+ >>> len(limit_text(lines, 2)) #only 1 newline is possible since we're limiting to two lines
+ 1
+ >>> lines = [Text(x) for x in 'foo\nbar\nbaz']
+ >>> len(limit_text(lines, 2)) #from bar to end
+ 7
+
+ """
+ # limit line lengths
+ new_line_idx = [i for i,x in enumerate(text_list) if x.text == '\n']
+ if len(new_line_idx) >= max_lines:
+ new_start = new_line_idx[-max_lines] + 1
+ text_list = text_list[new_start:]
+ return text_list
+
+class Text(object):
+ """Simple class to hold text and pre/postfix for it
+ """
+ def __init__(self, text, prefix='', postfix=''):
+ self.text = text
+ self.prefix = prefix
+ self.postfix = postfix
+
+ def __repr__(self):
+ return '%s%s%s' %(self.prefix, self.text, self.postfix)
+
+ @property
+ def is_char(self):
+ t = self.text
+ return len(t) == 1 and (t.isalnum() or t.isspace())
+ @property
+ def is_chord_prefix(self):
+ return self.text in CHORD_PREFIXES
+
+def main():
+ gtk.gdk.threads_init()
+ hm = get_hook_manager()
+ view = GTKKeyView(hm)
+ w = view.window
+ w.resize(360, 150)
+ w.set_keep_above(True) #ensure visibility
+ w.show()
+
+ try:
+ gtk.main()
+ except KeyboardInterrupt, e:
+ # kill the hook manager thread
+ view.hm.cancel()
+
+def test():
+ import doctest
+ doctest.testmod()
+
+if __name__ == '__main__':
+ main()
+
+
+
diff --git a/lyricstimer/pyxhook.py b/lyricstimer/pyxhook.py
new file mode 100644
index 0000000..387eb86
--- /dev/null
+++ b/lyricstimer/pyxhook.py
@@ -0,0 +1,359 @@
+#!/usr/bin/python
+# from pykeylogger http://pykeylogger.sourceforge.net/
+#
+# pyxhook -- an extension to emulate some of the PyHook library on linux.
+#
+# Copyright (C) 2008 Tim Alexander <dragonfyre13@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Thanks to Alex Badea <vamposdecampos@gmail.com> for writing the Record
+# demo for the xlib libraries. It helped me immensely working with these
+# in this library.
+#
+# Thanks to the python-xlib team. This wouldn't have been possible without
+# your code.
+#
+# This requires:
+# at least python-xlib 1.4
+# xwindows must have the "record" extension present, and active.
+#
+# This file has now been somewhat extensively modified by
+# Daniel Folkinshteyn <nanotube@users.sf.net>
+# So if there are any bugs, they are probably my fault. :)
+
+import sys
+import os
+import re
+import time
+import threading
+
+from Xlib import X, XK, display, error
+from Xlib.ext import record
+from Xlib.protocol import rq
+
+#######################################################################
+########################START CLASS DEF################################
+#######################################################################
+
+class HookManager(threading.Thread):
+ """This is the main class. Instantiate it, and you can hand it KeyDown and KeyUp (functions in your own code) which execute to parse the pyxhookkeyevent class that is returned.
+
+ This simply takes these two values for now:
+ KeyDown = The function to execute when a key is pressed, if it returns anything. It hands the function an argument that is the pyxhookkeyevent class.
+ KeyUp = The function to execute when a key is released, if it returns anything. It hands the function an argument that is the pyxhookkeyevent class.
+ """
+
+ def __init__(self):
+ threading.Thread.__init__(self)
+ self.finished = threading.Event()
+
+ # Give these some initial values
+ self.mouse_position_x = 0
+ self.mouse_position_y = 0
+ self.ison = {"shift":False, "caps":False}
+
+ # Compile our regex statements.
+ self.isshift = re.compile('^Shift')
+ self.iscaps = re.compile('^Caps_Lock')
+ self.shiftablechar = re.compile('^[a-z0-9]$|^minus$|^equal$|^bracketleft$|^bracketright$|^semicolon$|^backslash$|^apostrophe$|^comma$|^period$|^slash$|^grave$')
+ self.logrelease = re.compile('.*')
+ self.isspace = re.compile('^space$')
+
+ # Assign default function actions (do nothing).
+ self.KeyDown = lambda x: True
+ self.KeyUp = lambda x: True
+ self.MouseAllButtonsDown = lambda x: True
+ self.MouseAllButtonsUp = lambda x: True
+
+ self.contextEventMask = [X.KeyPress,X.MotionNotify]
+
+ # Hook to our display.
+ self.local_dpy = display.Display()
+ self.record_dpy = display.Display()
+
+ def run(self):
+ # Check if the extension is present
+ if not self.record_dpy.has_extension("RECORD"):
+ print "RECORD extension not found"
+ sys.exit(1)
+ r = self.record_dpy.record_get_version(0, 0)
+ print "RECORD extension version %d.%d" % (r.major_version, r.minor_version)
+
+ # Create a recording context; we only want key and mouse events
+ self.ctx = self.record_dpy.record_create_context(
+ 0,
+ [record.AllClients],
+ [{
+ 'core_requests': (0, 0),
+ 'core_replies': (0, 0),
+ 'ext_requests': (0, 0, 0, 0),
+ 'ext_replies': (0, 0, 0, 0),
+ 'delivered_events': (0, 0),
+ 'device_events': tuple(self.contextEventMask), #(X.KeyPress, X.ButtonPress),
+ 'errors': (0, 0),
+ 'client_started': False,
+ 'client_died': False,
+ }])
+
+ # Enable the context; this only returns after a call to record_disable_context,
+ # while calling the callback function in the meantime
+ self.record_dpy.record_enable_context(self.ctx, self.processevents)
+ # Finally free the context
+ self.record_dpy.record_free_context(self.ctx)
+
+ def cancel(self):
+ self.finished.set()
+ self.local_dpy.record_disable_context(self.ctx)
+ self.local_dpy.flush()
+
+ def printevent(self, event):
+ print event
+
+ def HookKeyboard(self):
+ pass
+ # We don't need to do anything here anymore, since the default mask
+ # is now set to contain X.KeyPress
+ #self.contextEventMask[0] = X.KeyPress
+
+ def HookMouse(self):
+ pass
+ # We don't need to do anything here anymore, since the default mask
+ # is now set to contain X.MotionNotify
+
+ # need mouse motion to track pointer position, since ButtonPress events
+ # don't carry that info.
+ #self.contextEventMask[1] = X.MotionNotify
+
+ def processevents(self, reply):
+ if reply.category != record.FromServer:
+ return
+ if reply.client_swapped:
+ print "* received swapped protocol data, cowardly ignored"
+ return
+ if not len(reply.data) or ord(reply.data[0]) < 2:
+ # not an event
+ return
+ data = reply.data
+ while len(data):
+ event, data = rq.EventField(None).parse_binary_value(data, self.record_dpy.display, None, None)
+ if event.type == X.KeyPress:
+ hookevent = self.keypressevent(event)
+ self.KeyDown(hookevent)
+ elif event.type == X.KeyRelease:
+ hookevent = self.keyreleaseevent(event)
+ self.KeyUp(hookevent)
+ elif event.type == X.ButtonPress:
+ hookevent = self.buttonpressevent(event)
+ self.MouseAllButtonsDown(hookevent)
+ elif event.type == X.ButtonRelease:
+ hookevent = self.buttonreleaseevent(event)
+ self.MouseAllButtonsUp(hookevent)
+ elif event.type == X.MotionNotify:
+ # use mouse moves to record mouse position, since press and release events
+ # do not give mouse position info (event.root_x and event.root_y have
+ # bogus info).
+ self.mousemoveevent(event)
+
+ #print "processing events...", event.type
+
+ def keypressevent(self, event):
+ matchto = self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))
+ if self.shiftablechar.match(self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))): ## This is a character that can be typed.
+ if self.ison["shift"] == False:
+ keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+ return self.makekeyhookevent(keysym, event)
+ else:
+ keysym = self.local_dpy.keycode_to_keysym(event.detail, 1)
+ return self.makekeyhookevent(keysym, event)
+ else: ## Not a typable character.
+ keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+ if self.isshift.match(matchto):
+ self.ison["shift"] = self.ison["shift"] + 1
+ elif self.iscaps.match(matchto):
+ if self.ison["caps"] == False:
+ self.ison["shift"] = self.ison["shift"] + 1
+ self.ison["caps"] = True
+ if self.ison["caps"] == True:
+ self.ison["shift"] = self.ison["shift"] - 1
+ self.ison["caps"] = False
+ return self.makekeyhookevent(keysym, event)
+
+ def keyreleaseevent(self, event):
+ if self.shiftablechar.match(self.lookup_keysym(self.local_dpy.keycode_to_keysym(event.detail, 0))):
+ if self.ison["shift"] == False:
+ keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+ else:
+ keysym = self.local_dpy.keycode_to_keysym(event.detail, 1)
+ else:
+ keysym = self.local_dpy.keycode_to_keysym(event.detail, 0)
+ matchto = self.lookup_keysym(keysym)
+ if self.isshift.match(matchto):
+ self.ison["shift"] = self.ison["shift"] - 1
+ return self.makekeyhookevent(keysym, event)
+
+ def buttonpressevent(self, event):
+ #self.clickx = self.rootx
+ #self.clicky = self.rooty
+ return self.makemousehookevent(event)
+
+ def buttonreleaseevent(self, event):
+ #if (self.clickx == self.rootx) and (self.clicky == self.rooty):
+ ##print "ButtonClick " + str(event.detail) + " x=" + str(self.rootx) + " y=" + str(self.rooty)
+ #if (event.detail == 1) or (event.detail == 2) or (event.detail == 3):
+ #self.captureclick()
+ #else:
+ #pass
+
+ return self.makemousehookevent(event)
+
+ # sys.stdout.write("ButtonDown " + str(event.detail) + " x=" + str(self.clickx) + " y=" + str(self.clicky) + "\n")
+ # sys.stdout.write("ButtonUp " + str(event.detail) + " x=" + str(self.rootx) + " y=" + str(self.rooty) + "\n")
+ #sys.stdout.flush()
+
+ def mousemoveevent(self, event):
+ self.mouse_position_x = event.root_x
+ self.mouse_position_y = event.root_y
+
+ # need the following because XK.keysym_to_string() only does printable chars
+ # rather than being the correct inverse of XK.string_to_keysym()
+ def lookup_keysym(self, keysym):
+ for name in dir(XK):
+ if name.startswith("XK_") and getattr(XK, name) == keysym:
+ return name.lstrip("XK_")
+ return "[%d]" % keysym
+
+ def asciivalue(self, keysym):
+ asciinum = XK.string_to_keysym(self.lookup_keysym(keysym))
+ if asciinum < 256:
+ return asciinum
+ else:
+ return 0
+
+ def makekeyhookevent(self, keysym, event):
+ storewm = self.xwindowinfo()
+ if event.type == X.KeyPress:
+ MessageName = "key down"
+ elif event.type == X.KeyRelease:
+ MessageName = "key up"
+ return pyxhookkeyevent(storewm["handle"], storewm["name"], storewm["class"], self.lookup_keysym(keysym), self.asciivalue(keysym), False, event.detail, MessageName)
+
+ def makemousehookevent(self, event):
+ storewm = self.xwindowinfo()
+ if event.detail == 1:
+ MessageName = "mouse left "
+ elif event.detail == 3:
+ MessageName = "mouse right "
+ elif event.detail == 2:
+ MessageName = "mouse middle "
+ elif event.detail == 5:
+ MessageName = "mouse wheel down "
+ elif event.detail == 4:
+ MessageName = "mouse wheel up "
+ else:
+ MessageName = "mouse " + str(event.detail) + " "
+
+ if event.type == X.ButtonPress:
+ MessageName = MessageName + "down"
+ elif event.type == X.ButtonRelease:
+ MessageName = MessageName + "up"
+ return pyxhookmouseevent(storewm["handle"], storewm["name"], storewm["class"], (self.mouse_position_x, self.mouse_position_y), MessageName)
+
+ def xwindowinfo(self):
+ try:
+ windowvar = self.local_dpy.get_input_focus().focus
+ wmname = windowvar.get_wm_name()
+ wmclass = windowvar.get_wm_class()
+ wmhandle = str(windowvar)[20:30]
+ except:
+ ## This is to keep things running smoothly. It almost never happens, but still...
+ return {"name":None, "class":None, "handle":None}
+ if (wmname == None) and (wmclass == None):
+ try:
+ windowvar = windowvar.query_tree().parent
+ wmname = windowvar.get_wm_name()
+ wmclass = windowvar.get_wm_class()
+ wmhandle = str(windowvar)[20:30]
+ except:
+ ## This is to keep things running smoothly. It almost never happens, but still...
+ return {"name":None, "class":None, "handle":None}
+ if wmclass == None:
+ return {"name":wmname, "class":wmclass, "handle":wmhandle}
+ else:
+ return {"name":wmname, "class":wmclass[0], "handle":wmhandle}
+
+class pyxhookkeyevent:
+ """This is the class that is returned with each key event.f
+ It simply creates the variables below in the class.
+
+ Window = The handle of the window.
+ WindowName = The name of the window.
+ WindowProcName = The backend process for the window.
+ Key = The key pressed, shifted to the correct caps value.
+ Ascii = An ascii representation of the key. It returns 0 if the ascii value is not between 31 and 256.
+ KeyID = This is just False for now. Under windows, it is the Virtual Key Code, but that's a windows-only thing.
+ ScanCode = Please don't use this. It differs for pretty much every type of keyboard. X11 abstracts this information anyway.
+ MessageName = "key down", "key up".
+ """
+
+ def __init__(self, Window, WindowName, WindowProcName, Key, Ascii, KeyID, ScanCode, MessageName):
+ self.Window = Window
+ self.WindowName = WindowName
+ self.WindowProcName = WindowProcName
+ self.Key = Key
+ self.Ascii = Ascii
+ self.KeyID = KeyID
+ self.ScanCode = ScanCode
+ self.MessageName = MessageName
+
+ def __str__(self):
+ return "Window Handle: " + str(self.Window) + "\nWindow Name: " + str(self.WindowName) + "\nWindow's Process Name: " + str(self.WindowProcName) + "\nKey Pressed: " + str(self.Key) + "\nAscii Value: " + str(self.Ascii) + "\nKeyID: " + str(self.KeyID) + "\nScanCode: " + str(self.ScanCode) + "\nMessageName: " + str(self.MessageName) + "\n"
+
+class pyxhookmouseevent:
+ """This is the class that is returned with each key event.f
+ It simply creates the variables below in the class.
+
+ Window = The handle of the window.
+ WindowName = The name of the window.
+ WindowProcName = The backend process for the window.
+ Position = 2-tuple (x,y) coordinates of the mouse click
+ MessageName = "mouse left|right|middle down", "mouse left|right|middle up".
+ """
+
+ def __init__(self, Window, WindowName, WindowProcName, Position, MessageName):
+ self.Window = Window
+ self.WindowName = WindowName
+ self.WindowProcName = WindowProcName
+ self.Position = Position
+ self.MessageName = MessageName
+
+ def __str__(self):
+ return "Window Handle: " + str(self.Window) + "\nWindow Name: " + str(self.WindowName) + "\nWindow's Process Name: " + str(self.WindowProcName) + "\nPosition: " + str(self.Position) + "\nMessageName: " + str(self.MessageName) + "\n"
+
+#######################################################################
+#########################END CLASS DEF#################################
+#######################################################################
+
+if __name__ == '__main__':
+ hm = HookManager()
+ hm.HookKeyboard()
+ hm.HookMouse()
+ hm.KeyDown = hm.printevent
+ hm.KeyUp = hm.printevent
+ hm.MouseAllButtonsDown = hm.printevent
+ hm.MouseAllButtonsUp = hm.printevent
+ hm.start()
+ time.sleep(10)
+ hm.cancel()
diff --git a/lyricstimer/timelyrics b/lyricstimer/timelyrics
new file mode 100755
index 0000000..2d6e071
--- /dev/null
+++ b/lyricstimer/timelyrics
@@ -0,0 +1,60 @@
+#!/usr/bin/python
+
+from pyxhook import HookManager
+from time import time
+import termios, sys
+import atexit
+
+if len(sys.argv)<2:
+ print "usage: timelyrics audio_filename"
+ exit()
+
+import pygame
+pygame.mixer.init()
+pygame.mixer.music.load(sys.argv[1])
+pygame.mixer.music.play()
+
+for arg in sys.argv:
+ print arg
+
+
+def enable_echo(fd, enabled):
+ (iflag, oflag, cflag, lflag, ispeed, ospeed, cc) \
+ = termios.tcgetattr(fd)
+ if enabled:
+ lflag |= termios.ECHO
+ else:
+ lflag &= ~termios.ECHO
+ new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
+ termios.tcsetattr(fd, termios.TCSANOW, new_attr)
+
+atexit.register(enable_echo, sys.stdin.fileno(), True)
+
+enable_echo( sys.stdin.fileno(),False)
+
+start=0.0
+down=0.0
+state="up"
+count=0
+
+def handle_up (event):
+ global start,down,state,count
+ if count>0:
+ print "[",down,",",time()-(start+down),"]"
+ state="up"
+ count+=1
+
+def handle_down (event):
+ global start,down,state
+ if state=="up":
+ down=time()-start
+ state="down"
+
+
+hm = HookManager()
+hm.HookKeyboard()
+hm.KeyUp = handle_up
+hm.KeyDown = handle_down
+start=time()
+print "started:",start
+hm.start() \ No newline at end of file