#!/usr/bin/perl
#
# magmakeys(.pl)
# by Stefan Tomanek <stefan@pico.ruhr.de>
# http://stefans.datenbruch.de/magmakey/
# 
# Magmakey is a system wide hotkey daemon.
# It watches all connected input devices for
# key and switch events and can launch arbitrary
# commands when certain events are observed.
#
# It is designed to handle hardware specific keys
# like wireless or suspend controls that usually
# are not user specific

use strict;
use Config;
use Getopt::Long;
use IO::File;
use threads;
use threads::shared;

use constant VERSION => "0.1";

# we need these to connect to HAL and receive the list
# of connected input devices as well as hotplug events
# 
# but we only load the modules if needed
#use Net::DBus;
#use Net::DBus::Reactor;

my $use_hal = 1;
my @manual_devs = ();


# global event tables
my %types;
$types{EV_VERSION} = 0x010000;
$types{EV_SYN} = 0x00;
$types{EV_KEY} = 0x01;
$types{EV_REL} = 0x02;
$types{EV_ABS} = 0x03;
$types{EV_MSC} = 0x04;
$types{EV_SW} = 0x05;
$types{EV_LED} = 0x11;
$types{EV_SND} = 0x12;
$types{EV_REP} = 0x14;
$types{EV_FF} = 0x15;
$types{EV_PWR} = 0x16;
$types{EV_FF_STATUS} = 0x17;
$types{EV_MAX} = 0x1f;

my %codes;
$codes{KEY_RESERVED} = {type => $types{EV_KEY}, code => 0};
$codes{KEY_ESC} = {type => $types{EV_KEY}, code => 1};
$codes{KEY_1} = {type => $types{EV_KEY}, code => 2};
$codes{KEY_2} = {type => $types{EV_KEY}, code => 3};
$codes{KEY_3} = {type => $types{EV_KEY}, code => 4};
$codes{KEY_4} = {type => $types{EV_KEY}, code => 5};
$codes{KEY_5} = {type => $types{EV_KEY}, code => 6};
$codes{KEY_6} = {type => $types{EV_KEY}, code => 7};
$codes{KEY_7} = {type => $types{EV_KEY}, code => 8};
$codes{KEY_8} = {type => $types{EV_KEY}, code => 9};
$codes{KEY_9} = {type => $types{EV_KEY}, code => 10};
$codes{KEY_0} = {type => $types{EV_KEY}, code => 11};
$codes{KEY_MINUS} = {type => $types{EV_KEY}, code => 12};
$codes{KEY_EQUAL} = {type => $types{EV_KEY}, code => 13};
$codes{KEY_BACKSPACE} = {type => $types{EV_KEY}, code => 14};
$codes{KEY_TAB} = {type => $types{EV_KEY}, code => 15};
$codes{KEY_Q} = {type => $types{EV_KEY}, code => 16};
$codes{KEY_W} = {type => $types{EV_KEY}, code => 17};
$codes{KEY_E} = {type => $types{EV_KEY}, code => 18};
$codes{KEY_R} = {type => $types{EV_KEY}, code => 19};
$codes{KEY_T} = {type => $types{EV_KEY}, code => 20};
$codes{KEY_Y} = {type => $types{EV_KEY}, code => 21};
$codes{KEY_U} = {type => $types{EV_KEY}, code => 22};
$codes{KEY_I} = {type => $types{EV_KEY}, code => 23};
$codes{KEY_O} = {type => $types{EV_KEY}, code => 24};
$codes{KEY_P} = {type => $types{EV_KEY}, code => 25};
$codes{KEY_LEFTBRACE} = {type => $types{EV_KEY}, code => 26};
$codes{KEY_RIGHTBRACE} = {type => $types{EV_KEY}, code => 27};
$codes{KEY_ENTER} = {type => $types{EV_KEY}, code => 28};
$codes{KEY_LEFTCTRL} = {type => $types{EV_KEY}, code => 29};
$codes{KEY_A} = {type => $types{EV_KEY}, code => 30};
$codes{KEY_S} = {type => $types{EV_KEY}, code => 31};
$codes{KEY_D} = {type => $types{EV_KEY}, code => 32};
$codes{KEY_F} = {type => $types{EV_KEY}, code => 33};
$codes{KEY_G} = {type => $types{EV_KEY}, code => 34};
$codes{KEY_H} = {type => $types{EV_KEY}, code => 35};
$codes{KEY_J} = {type => $types{EV_KEY}, code => 36};
$codes{KEY_K} = {type => $types{EV_KEY}, code => 37};
$codes{KEY_L} = {type => $types{EV_KEY}, code => 38};
$codes{KEY_SEMICOLON} = {type => $types{EV_KEY}, code => 39};
$codes{KEY_APOSTROPHE} = {type => $types{EV_KEY}, code => 40};
$codes{KEY_GRAVE} = {type => $types{EV_KEY}, code => 41};
$codes{KEY_LEFTSHIFT} = {type => $types{EV_KEY}, code => 42};
$codes{KEY_BACKSLASH} = {type => $types{EV_KEY}, code => 43};
$codes{KEY_Z} = {type => $types{EV_KEY}, code => 44};
$codes{KEY_X} = {type => $types{EV_KEY}, code => 45};
$codes{KEY_C} = {type => $types{EV_KEY}, code => 46};
$codes{KEY_V} = {type => $types{EV_KEY}, code => 47};
$codes{KEY_B} = {type => $types{EV_KEY}, code => 48};
$codes{KEY_N} = {type => $types{EV_KEY}, code => 49};
$codes{KEY_M} = {type => $types{EV_KEY}, code => 50};
$codes{KEY_COMMA} = {type => $types{EV_KEY}, code => 51};
$codes{KEY_DOT} = {type => $types{EV_KEY}, code => 52};
$codes{KEY_SLASH} = {type => $types{EV_KEY}, code => 53};
$codes{KEY_RIGHTSHIFT} = {type => $types{EV_KEY}, code => 54};
$codes{KEY_KPASTERISK} = {type => $types{EV_KEY}, code => 55};
$codes{KEY_LEFTALT} = {type => $types{EV_KEY}, code => 56};
$codes{KEY_SPACE} = {type => $types{EV_KEY}, code => 57};
$codes{KEY_CAPSLOCK} = {type => $types{EV_KEY}, code => 58};
$codes{KEY_F1} = {type => $types{EV_KEY}, code => 59};
$codes{KEY_F2} = {type => $types{EV_KEY}, code => 60};
$codes{KEY_F3} = {type => $types{EV_KEY}, code => 61};
$codes{KEY_F4} = {type => $types{EV_KEY}, code => 62};
$codes{KEY_F5} = {type => $types{EV_KEY}, code => 63};
$codes{KEY_F6} = {type => $types{EV_KEY}, code => 64};
$codes{KEY_F7} = {type => $types{EV_KEY}, code => 65};
$codes{KEY_F8} = {type => $types{EV_KEY}, code => 66};
$codes{KEY_F9} = {type => $types{EV_KEY}, code => 67};
$codes{KEY_F10} = {type => $types{EV_KEY}, code => 68};
$codes{KEY_NUMLOCK} = {type => $types{EV_KEY}, code => 69};
$codes{KEY_SCROLLLOCK} = {type => $types{EV_KEY}, code => 70};
$codes{KEY_KP7} = {type => $types{EV_KEY}, code => 71};
$codes{KEY_KP8} = {type => $types{EV_KEY}, code => 72};
$codes{KEY_KP9} = {type => $types{EV_KEY}, code => 73};
$codes{KEY_KPMINUS} = {type => $types{EV_KEY}, code => 74};
$codes{KEY_KP4} = {type => $types{EV_KEY}, code => 75};
$codes{KEY_KP5} = {type => $types{EV_KEY}, code => 76};
$codes{KEY_KP6} = {type => $types{EV_KEY}, code => 77};
$codes{KEY_KPPLUS} = {type => $types{EV_KEY}, code => 78};
$codes{KEY_KP1} = {type => $types{EV_KEY}, code => 79};
$codes{KEY_KP2} = {type => $types{EV_KEY}, code => 80};
$codes{KEY_KP3} = {type => $types{EV_KEY}, code => 81};
$codes{KEY_KP0} = {type => $types{EV_KEY}, code => 82};
$codes{KEY_KPDOT} = {type => $types{EV_KEY}, code => 83};
$codes{KEY_ZENKAKUHANKAKU} = {type => $types{EV_KEY}, code => 85};
$codes{KEY_102ND} = {type => $types{EV_KEY}, code => 86};
$codes{KEY_F11} = {type => $types{EV_KEY}, code => 87};
$codes{KEY_F12} = {type => $types{EV_KEY}, code => 88};
$codes{KEY_RO} = {type => $types{EV_KEY}, code => 89};
$codes{KEY_KATAKANA} = {type => $types{EV_KEY}, code => 90};
$codes{KEY_HIRAGANA} = {type => $types{EV_KEY}, code => 91};
$codes{KEY_HENKAN} = {type => $types{EV_KEY}, code => 92};
$codes{KEY_KATAKANAHIRAGANA} = {type => $types{EV_KEY}, code => 93};
$codes{KEY_MUHENKAN} = {type => $types{EV_KEY}, code => 94};
$codes{KEY_KPJPCOMMA} = {type => $types{EV_KEY}, code => 95};
$codes{KEY_KPENTER} = {type => $types{EV_KEY}, code => 96};
$codes{KEY_RIGHTCTRL} = {type => $types{EV_KEY}, code => 97};
$codes{KEY_KPSLASH} = {type => $types{EV_KEY}, code => 98};
$codes{KEY_SYSRQ} = {type => $types{EV_KEY}, code => 99};
$codes{KEY_RIGHTALT} = {type => $types{EV_KEY}, code => 100};
$codes{KEY_LINEFEED} = {type => $types{EV_KEY}, code => 101};
$codes{KEY_HOME} = {type => $types{EV_KEY}, code => 102};
$codes{KEY_UP} = {type => $types{EV_KEY}, code => 103};
$codes{KEY_PAGEUP} = {type => $types{EV_KEY}, code => 104};
$codes{KEY_LEFT} = {type => $types{EV_KEY}, code => 105};
$codes{KEY_RIGHT} = {type => $types{EV_KEY}, code => 106};
$codes{KEY_END} = {type => $types{EV_KEY}, code => 107};
$codes{KEY_DOWN} = {type => $types{EV_KEY}, code => 108};
$codes{KEY_PAGEDOWN} = {type => $types{EV_KEY}, code => 109};
$codes{KEY_INSERT} = {type => $types{EV_KEY}, code => 110};
$codes{KEY_DELETE} = {type => $types{EV_KEY}, code => 111};
$codes{KEY_MACRO} = {type => $types{EV_KEY}, code => 112};
$codes{KEY_MUTE} = {type => $types{EV_KEY}, code => 113};
$codes{KEY_VOLUMEDOWN} = {type => $types{EV_KEY}, code => 114};
$codes{KEY_VOLUMEUP} = {type => $types{EV_KEY}, code => 115};
$codes{KEY_POWER} = {type => $types{EV_KEY}, code => 116};
$codes{KEY_KPEQUAL} = {type => $types{EV_KEY}, code => 117};
$codes{KEY_KPPLUSMINUS} = {type => $types{EV_KEY}, code => 118};
$codes{KEY_PAUSE} = {type => $types{EV_KEY}, code => 119};
$codes{KEY_KPCOMMA} = {type => $types{EV_KEY}, code => 121};
$codes{KEY_HANGEUL} = {type => $types{EV_KEY}, code => 122};
$codes{KEY_HANJA} = {type => $types{EV_KEY}, code => 123};
$codes{KEY_YEN} = {type => $types{EV_KEY}, code => 124};
$codes{KEY_LEFTMETA} = {type => $types{EV_KEY}, code => 125};
$codes{KEY_RIGHTMETA} = {type => $types{EV_KEY}, code => 126};
$codes{KEY_COMPOSE} = {type => $types{EV_KEY}, code => 127};
$codes{KEY_STOP} = {type => $types{EV_KEY}, code => 128};
$codes{KEY_AGAIN} = {type => $types{EV_KEY}, code => 129};
$codes{KEY_PROPS} = {type => $types{EV_KEY}, code => 130};
$codes{KEY_UNDO} = {type => $types{EV_KEY}, code => 131};
$codes{KEY_FRONT} = {type => $types{EV_KEY}, code => 132};
$codes{KEY_COPY} = {type => $types{EV_KEY}, code => 133};
$codes{KEY_OPEN} = {type => $types{EV_KEY}, code => 134};
$codes{KEY_PASTE} = {type => $types{EV_KEY}, code => 135};
$codes{KEY_FIND} = {type => $types{EV_KEY}, code => 136};
$codes{KEY_CUT} = {type => $types{EV_KEY}, code => 137};
$codes{KEY_HELP} = {type => $types{EV_KEY}, code => 138};
$codes{KEY_MENU} = {type => $types{EV_KEY}, code => 139};
$codes{KEY_CALC} = {type => $types{EV_KEY}, code => 140};
$codes{KEY_SETUP} = {type => $types{EV_KEY}, code => 141};
$codes{KEY_SLEEP} = {type => $types{EV_KEY}, code => 142};
$codes{KEY_WAKEUP} = {type => $types{EV_KEY}, code => 143};
$codes{KEY_FILE} = {type => $types{EV_KEY}, code => 144};
$codes{KEY_SENDFILE} = {type => $types{EV_KEY}, code => 145};
$codes{KEY_DELETEFILE} = {type => $types{EV_KEY}, code => 146};
$codes{KEY_XFER} = {type => $types{EV_KEY}, code => 147};
$codes{KEY_PROG1} = {type => $types{EV_KEY}, code => 148};
$codes{KEY_PROG2} = {type => $types{EV_KEY}, code => 149};
$codes{KEY_WWW} = {type => $types{EV_KEY}, code => 150};
$codes{KEY_MSDOS} = {type => $types{EV_KEY}, code => 151};
$codes{KEY_COFFEE} = {type => $types{EV_KEY}, code => 152};
$codes{KEY_DIRECTION} = {type => $types{EV_KEY}, code => 153};
$codes{KEY_CYCLEWINDOWS} = {type => $types{EV_KEY}, code => 154};
$codes{KEY_MAIL} = {type => $types{EV_KEY}, code => 155};
$codes{KEY_BOOKMARKS} = {type => $types{EV_KEY}, code => 156};
$codes{KEY_COMPUTER} = {type => $types{EV_KEY}, code => 157};
$codes{KEY_BACK} = {type => $types{EV_KEY}, code => 158};
$codes{KEY_FORWARD} = {type => $types{EV_KEY}, code => 159};
$codes{KEY_CLOSECD} = {type => $types{EV_KEY}, code => 160};
$codes{KEY_EJECTCD} = {type => $types{EV_KEY}, code => 161};
$codes{KEY_EJECTCLOSECD} = {type => $types{EV_KEY}, code => 162};
$codes{KEY_NEXTSONG} = {type => $types{EV_KEY}, code => 163};
$codes{KEY_PLAYPAUSE} = {type => $types{EV_KEY}, code => 164};
$codes{KEY_PREVIOUSSONG} = {type => $types{EV_KEY}, code => 165};
$codes{KEY_STOPCD} = {type => $types{EV_KEY}, code => 166};
$codes{KEY_RECORD} = {type => $types{EV_KEY}, code => 167};
$codes{KEY_REWIND} = {type => $types{EV_KEY}, code => 168};
$codes{KEY_PHONE} = {type => $types{EV_KEY}, code => 169};
$codes{KEY_ISO} = {type => $types{EV_KEY}, code => 170};
$codes{KEY_CONFIG} = {type => $types{EV_KEY}, code => 171};
$codes{KEY_HOMEPAGE} = {type => $types{EV_KEY}, code => 172};
$codes{KEY_REFRESH} = {type => $types{EV_KEY}, code => 173};
$codes{KEY_EXIT} = {type => $types{EV_KEY}, code => 174};
$codes{KEY_MOVE} = {type => $types{EV_KEY}, code => 175};
$codes{KEY_EDIT} = {type => $types{EV_KEY}, code => 176};
$codes{KEY_SCROLLUP} = {type => $types{EV_KEY}, code => 177};
$codes{KEY_SCROLLDOWN} = {type => $types{EV_KEY}, code => 178};
$codes{KEY_KPLEFTPAREN} = {type => $types{EV_KEY}, code => 179};
$codes{KEY_KPRIGHTPAREN} = {type => $types{EV_KEY}, code => 180};
$codes{KEY_NEW} = {type => $types{EV_KEY}, code => 181};
$codes{KEY_REDO} = {type => $types{EV_KEY}, code => 182};
$codes{KEY_F13} = {type => $types{EV_KEY}, code => 183};
$codes{KEY_F14} = {type => $types{EV_KEY}, code => 184};
$codes{KEY_F15} = {type => $types{EV_KEY}, code => 185};
$codes{KEY_F16} = {type => $types{EV_KEY}, code => 186};
$codes{KEY_F17} = {type => $types{EV_KEY}, code => 187};
$codes{KEY_F18} = {type => $types{EV_KEY}, code => 188};
$codes{KEY_F19} = {type => $types{EV_KEY}, code => 189};
$codes{KEY_F20} = {type => $types{EV_KEY}, code => 190};
$codes{KEY_F21} = {type => $types{EV_KEY}, code => 191};
$codes{KEY_F22} = {type => $types{EV_KEY}, code => 192};
$codes{KEY_F23} = {type => $types{EV_KEY}, code => 193};
$codes{KEY_F24} = {type => $types{EV_KEY}, code => 194};
$codes{KEY_PLAYCD} = {type => $types{EV_KEY}, code => 200};
$codes{KEY_PAUSECD} = {type => $types{EV_KEY}, code => 201};
$codes{KEY_PROG3} = {type => $types{EV_KEY}, code => 202};
$codes{KEY_PROG4} = {type => $types{EV_KEY}, code => 203};
$codes{KEY_SUSPEND} = {type => $types{EV_KEY}, code => 205};
$codes{KEY_CLOSE} = {type => $types{EV_KEY}, code => 206};
$codes{KEY_PLAY} = {type => $types{EV_KEY}, code => 207};
$codes{KEY_FASTFORWARD} = {type => $types{EV_KEY}, code => 208};
$codes{KEY_BASSBOOST} = {type => $types{EV_KEY}, code => 209};
$codes{KEY_PRINT} = {type => $types{EV_KEY}, code => 210};
$codes{KEY_HP} = {type => $types{EV_KEY}, code => 211};
$codes{KEY_CAMERA} = {type => $types{EV_KEY}, code => 212};
$codes{KEY_SOUND} = {type => $types{EV_KEY}, code => 213};
$codes{KEY_QUESTION} = {type => $types{EV_KEY}, code => 214};
$codes{KEY_EMAIL} = {type => $types{EV_KEY}, code => 215};
$codes{KEY_CHAT} = {type => $types{EV_KEY}, code => 216};
$codes{KEY_SEARCH} = {type => $types{EV_KEY}, code => 217};
$codes{KEY_CONNECT} = {type => $types{EV_KEY}, code => 218};
$codes{KEY_FINANCE} = {type => $types{EV_KEY}, code => 219};
$codes{KEY_SPORT} = {type => $types{EV_KEY}, code => 220};
$codes{KEY_SHOP} = {type => $types{EV_KEY}, code => 221};
$codes{KEY_ALTERASE} = {type => $types{EV_KEY}, code => 222};
$codes{KEY_CANCEL} = {type => $types{EV_KEY}, code => 223};
$codes{KEY_BRIGHTNESSDOWN} = {type => $types{EV_KEY}, code => 224};
$codes{KEY_BRIGHTNESSUP} = {type => $types{EV_KEY}, code => 225};
$codes{KEY_MEDIA} = {type => $types{EV_KEY}, code => 226};
$codes{KEY_SWITCHVIDEOMODE} = {type => $types{EV_KEY}, code => 227};
$codes{KEY_KBDILLUMTOGGLE} = {type => $types{EV_KEY}, code => 228};
$codes{KEY_KBDILLUMDOWN} = {type => $types{EV_KEY}, code => 229};
$codes{KEY_KBDILLUMUP} = {type => $types{EV_KEY}, code => 230};
$codes{KEY_SEND} = {type => $types{EV_KEY}, code => 231};
$codes{KEY_REPLY} = {type => $types{EV_KEY}, code => 232};
$codes{KEY_FORWARDMAIL} = {type => $types{EV_KEY}, code => 233};
$codes{KEY_SAVE} = {type => $types{EV_KEY}, code => 234};
$codes{KEY_DOCUMENTS} = {type => $types{EV_KEY}, code => 235};
$codes{KEY_BATTERY} = {type => $types{EV_KEY}, code => 236};
$codes{KEY_BLUETOOTH} = {type => $types{EV_KEY}, code => 237};
$codes{KEY_WLAN} = {type => $types{EV_KEY}, code => 238};
$codes{KEY_UWB} = {type => $types{EV_KEY}, code => 239};
$codes{KEY_UNKNOWN} = {type => $types{EV_KEY}, code => 240};
$codes{KEY_VIDEO_NEXT} = {type => $types{EV_KEY}, code => 241};
$codes{KEY_VIDEO_PREV} = {type => $types{EV_KEY}, code => 242};
$codes{KEY_BRIGHTNESS_CYCLE} = {type => $types{EV_KEY}, code => 243};
$codes{KEY_BRIGHTNESS_ZERO} = {type => $types{EV_KEY}, code => 244};
$codes{KEY_DISPLAY_OFF} = {type => $types{EV_KEY}, code => 245};
$codes{KEY_WIMAX} = {type => $types{EV_KEY}, code => 246};
$codes{KEY_OK} = {type => $types{EV_KEY}, code => 0x160};
$codes{KEY_SELECT} = {type => $types{EV_KEY}, code => 0x161};
$codes{KEY_GOTO} = {type => $types{EV_KEY}, code => 0x162};
$codes{KEY_CLEAR} = {type => $types{EV_KEY}, code => 0x163};
$codes{KEY_POWER2} = {type => $types{EV_KEY}, code => 0x164};
$codes{KEY_OPTION} = {type => $types{EV_KEY}, code => 0x165};
$codes{KEY_INFO} = {type => $types{EV_KEY}, code => 0x166};
$codes{KEY_TIME} = {type => $types{EV_KEY}, code => 0x167};
$codes{KEY_VENDOR} = {type => $types{EV_KEY}, code => 0x168};
$codes{KEY_ARCHIVE} = {type => $types{EV_KEY}, code => 0x169};
$codes{KEY_PROGRAM} = {type => $types{EV_KEY}, code => 0x16a};
$codes{KEY_CHANNEL} = {type => $types{EV_KEY}, code => 0x16b};
$codes{KEY_FAVORITES} = {type => $types{EV_KEY}, code => 0x16c};
$codes{KEY_EPG} = {type => $types{EV_KEY}, code => 0x16d};
$codes{KEY_PVR} = {type => $types{EV_KEY}, code => 0x16e};
$codes{KEY_MHP} = {type => $types{EV_KEY}, code => 0x16f};
$codes{KEY_LANGUAGE} = {type => $types{EV_KEY}, code => 0x170};
$codes{KEY_TITLE} = {type => $types{EV_KEY}, code => 0x171};
$codes{KEY_SUBTITLE} = {type => $types{EV_KEY}, code => 0x172};
$codes{KEY_ANGLE} = {type => $types{EV_KEY}, code => 0x173};
$codes{KEY_ZOOM} = {type => $types{EV_KEY}, code => 0x174};
$codes{KEY_MODE} = {type => $types{EV_KEY}, code => 0x175};
$codes{KEY_KEYBOARD} = {type => $types{EV_KEY}, code => 0x176};
$codes{KEY_SCREEN} = {type => $types{EV_KEY}, code => 0x177};
$codes{KEY_PC} = {type => $types{EV_KEY}, code => 0x178};
$codes{KEY_TV} = {type => $types{EV_KEY}, code => 0x179};
$codes{KEY_TV2} = {type => $types{EV_KEY}, code => 0x17a};
$codes{KEY_VCR} = {type => $types{EV_KEY}, code => 0x17b};
$codes{KEY_VCR2} = {type => $types{EV_KEY}, code => 0x17c};
$codes{KEY_SAT} = {type => $types{EV_KEY}, code => 0x17d};
$codes{KEY_SAT2} = {type => $types{EV_KEY}, code => 0x17e};
$codes{KEY_CD} = {type => $types{EV_KEY}, code => 0x17f};
$codes{KEY_TAPE} = {type => $types{EV_KEY}, code => 0x180};
$codes{KEY_RADIO} = {type => $types{EV_KEY}, code => 0x181};
$codes{KEY_TUNER} = {type => $types{EV_KEY}, code => 0x182};
$codes{KEY_PLAYER} = {type => $types{EV_KEY}, code => 0x183};
$codes{KEY_TEXT} = {type => $types{EV_KEY}, code => 0x184};
$codes{KEY_DVD} = {type => $types{EV_KEY}, code => 0x185};
$codes{KEY_AUX} = {type => $types{EV_KEY}, code => 0x186};
$codes{KEY_MP3} = {type => $types{EV_KEY}, code => 0x187};
$codes{KEY_AUDIO} = {type => $types{EV_KEY}, code => 0x188};
$codes{KEY_VIDEO} = {type => $types{EV_KEY}, code => 0x189};
$codes{KEY_DIRECTORY} = {type => $types{EV_KEY}, code => 0x18a};
$codes{KEY_LIST} = {type => $types{EV_KEY}, code => 0x18b};
$codes{KEY_MEMO} = {type => $types{EV_KEY}, code => 0x18c};
$codes{KEY_CALENDAR} = {type => $types{EV_KEY}, code => 0x18d};
$codes{KEY_RED} = {type => $types{EV_KEY}, code => 0x18e};
$codes{KEY_GREEN} = {type => $types{EV_KEY}, code => 0x18f};
$codes{KEY_YELLOW} = {type => $types{EV_KEY}, code => 0x190};
$codes{KEY_BLUE} = {type => $types{EV_KEY}, code => 0x191};
$codes{KEY_CHANNELUP} = {type => $types{EV_KEY}, code => 0x192};
$codes{KEY_CHANNELDOWN} = {type => $types{EV_KEY}, code => 0x193};
$codes{KEY_FIRST} = {type => $types{EV_KEY}, code => 0x194};
$codes{KEY_LAST} = {type => $types{EV_KEY}, code => 0x195};
$codes{KEY_AB} = {type => $types{EV_KEY}, code => 0x196};
$codes{KEY_NEXT} = {type => $types{EV_KEY}, code => 0x197};
$codes{KEY_RESTART} = {type => $types{EV_KEY}, code => 0x198};
$codes{KEY_SLOW} = {type => $types{EV_KEY}, code => 0x199};
$codes{KEY_SHUFFLE} = {type => $types{EV_KEY}, code => 0x19a};
$codes{KEY_BREAK} = {type => $types{EV_KEY}, code => 0x19b};
$codes{KEY_PREVIOUS} = {type => $types{EV_KEY}, code => 0x19c};
$codes{KEY_DIGITS} = {type => $types{EV_KEY}, code => 0x19d};
$codes{KEY_TEEN} = {type => $types{EV_KEY}, code => 0x19e};
$codes{KEY_TWEN} = {type => $types{EV_KEY}, code => 0x19f};
$codes{KEY_VIDEOPHONE} = {type => $types{EV_KEY}, code => 0x1a0};
$codes{KEY_GAMES} = {type => $types{EV_KEY}, code => 0x1a1};
$codes{KEY_ZOOMIN} = {type => $types{EV_KEY}, code => 0x1a2};
$codes{KEY_ZOOMOUT} = {type => $types{EV_KEY}, code => 0x1a3};
$codes{KEY_ZOOMRESET} = {type => $types{EV_KEY}, code => 0x1a4};
$codes{KEY_WORDPROCESSOR} = {type => $types{EV_KEY}, code => 0x1a5};
$codes{KEY_EDITOR} = {type => $types{EV_KEY}, code => 0x1a6};
$codes{KEY_SPREADSHEET} = {type => $types{EV_KEY}, code => 0x1a7};
$codes{KEY_GRAPHICSEDITOR} = {type => $types{EV_KEY}, code => 0x1a8};
$codes{KEY_PRESENTATION} = {type => $types{EV_KEY}, code => 0x1a9};
$codes{KEY_DATABASE} = {type => $types{EV_KEY}, code => 0x1aa};
$codes{KEY_NEWS} = {type => $types{EV_KEY}, code => 0x1ab};
$codes{KEY_VOICEMAIL} = {type => $types{EV_KEY}, code => 0x1ac};
$codes{KEY_ADDRESSBOOK} = {type => $types{EV_KEY}, code => 0x1ad};
$codes{KEY_MESSENGER} = {type => $types{EV_KEY}, code => 0x1ae};
$codes{KEY_DISPLAYTOGGLE} = {type => $types{EV_KEY}, code => 0x1af};
$codes{KEY_SPELLCHECK} = {type => $types{EV_KEY}, code => 0x1b0};
$codes{KEY_LOGOFF} = {type => $types{EV_KEY}, code => 0x1b1};
$codes{KEY_DOLLAR} = {type => $types{EV_KEY}, code => 0x1b2};
$codes{KEY_EURO} = {type => $types{EV_KEY}, code => 0x1b3};
$codes{KEY_FRAMEBACK} = {type => $types{EV_KEY}, code => 0x1b4};
$codes{KEY_FRAMEFORWARD} = {type => $types{EV_KEY}, code => 0x1b5};
$codes{KEY_CONTEXT_MENU} = {type => $types{EV_KEY}, code => 0x1b6};
$codes{KEY_MEDIA_REPEAT} = {type => $types{EV_KEY}, code => 0x1b7};
$codes{KEY_DEL_EOL} = {type => $types{EV_KEY}, code => 0x1c0};
$codes{KEY_DEL_EOS} = {type => $types{EV_KEY}, code => 0x1c1};
$codes{KEY_INS_LINE} = {type => $types{EV_KEY}, code => 0x1c2};
$codes{KEY_DEL_LINE} = {type => $types{EV_KEY}, code => 0x1c3};
$codes{KEY_FN} = {type => $types{EV_KEY}, code => 0x1d0};
$codes{KEY_FN_ESC} = {type => $types{EV_KEY}, code => 0x1d1};
$codes{KEY_FN_F1} = {type => $types{EV_KEY}, code => 0x1d2};
$codes{KEY_FN_F2} = {type => $types{EV_KEY}, code => 0x1d3};
$codes{KEY_FN_F3} = {type => $types{EV_KEY}, code => 0x1d4};
$codes{KEY_FN_F4} = {type => $types{EV_KEY}, code => 0x1d5};
$codes{KEY_FN_F5} = {type => $types{EV_KEY}, code => 0x1d6};
$codes{KEY_FN_F6} = {type => $types{EV_KEY}, code => 0x1d7};
$codes{KEY_FN_F7} = {type => $types{EV_KEY}, code => 0x1d8};
$codes{KEY_FN_F8} = {type => $types{EV_KEY}, code => 0x1d9};
$codes{KEY_FN_F9} = {type => $types{EV_KEY}, code => 0x1da};
$codes{KEY_FN_F10} = {type => $types{EV_KEY}, code => 0x1db};
$codes{KEY_FN_F11} = {type => $types{EV_KEY}, code => 0x1dc};
$codes{KEY_FN_F12} = {type => $types{EV_KEY}, code => 0x1dd};
$codes{KEY_FN_1} = {type => $types{EV_KEY}, code => 0x1de};
$codes{KEY_FN_2} = {type => $types{EV_KEY}, code => 0x1df};
$codes{KEY_FN_D} = {type => $types{EV_KEY}, code => 0x1e0};
$codes{KEY_FN_E} = {type => $types{EV_KEY}, code => 0x1e1};
$codes{KEY_FN_F} = {type => $types{EV_KEY}, code => 0x1e2};
$codes{KEY_FN_S} = {type => $types{EV_KEY}, code => 0x1e3};
$codes{KEY_FN_B} = {type => $types{EV_KEY}, code => 0x1e4};
$codes{KEY_BRL_DOT1} = {type => $types{EV_KEY}, code => 0x1f1};
$codes{KEY_BRL_DOT2} = {type => $types{EV_KEY}, code => 0x1f2};
$codes{KEY_BRL_DOT3} = {type => $types{EV_KEY}, code => 0x1f3};
$codes{KEY_BRL_DOT4} = {type => $types{EV_KEY}, code => 0x1f4};
$codes{KEY_BRL_DOT5} = {type => $types{EV_KEY}, code => 0x1f5};
$codes{KEY_BRL_DOT6} = {type => $types{EV_KEY}, code => 0x1f6};
$codes{KEY_BRL_DOT7} = {type => $types{EV_KEY}, code => 0x1f7};
$codes{KEY_BRL_DOT8} = {type => $types{EV_KEY}, code => 0x1f8};
$codes{KEY_BRL_DOT9} = {type => $types{EV_KEY}, code => 0x1f9};
$codes{KEY_BRL_DOT10} = {type => $types{EV_KEY}, code => 0x1fa};
$codes{KEY_MAX} = {type => $types{EV_KEY}, code => 0x1ff};

$codes{SW_LID} = {type => $types{EV_SW}, code => 0x00};
$codes{SW_TABLET_MODE} = {type => $types{EV_SW}, code => 0x01};
$codes{SW_HEADPHONE_INSERT} = {type => $types{EV_SW}, code => 0x02};
$codes{SW_RFKILL_ALL} = {type => $types{EV_SW}, code => 0x03};
$codes{SW_MAX} = {type => $types{EV_SW}, code => 0x0f};

# invert tables for lookups
my %inv_types;
for my $key (keys %types) {
    $inv_types{$types{$key}} = $key;
}
my %inv_codes;
for my $key (keys %codes) {
    my $type = $codes{$key}{type};
    my $code = $codes{$key}{code};
    $inv_codes{$type}{$code} = {type => $inv_types{$type}, code => $key};
}

# global variables
my $struct_len = ($Config{longsize} * 2) +
                 ($Config{i16size} * 2) +
                 ($Config{i32size});
my $hal;
# shared in case we reload the config file
my %handlers :shared;

my %pressed_keys :shared;

# default config values
my $dump_events = 0;
my $help = 0;
my $config_file = "";

sub say {
    my ($priority, @message) = @_;
    # TODO filter messages by priority (debug, info etc.)
    print STDERR @message, "\n";
}

sub device_added_cb {
    my ($id) = @_;
    my $dev = $hal->get_object($id, 'org.freedesktop.Hal.Device');
    my $props = $dev->GetAllProperties;
    if ($props->{"linux.subsystem"} eq "input") {
        # check input capabilities
        # only watch devices with keys, switches or buttons
        for my $cap (@{$props->{"info.capabilities"}}) {
            if ($cap eq "input.keyboard" ||
                $cap eq "input.keypad" ||
                $cap eq "input.keys" ||
                $cap eq "input.switch" ||
                $cap eq "buttons") {
                my $t = threads->create(\&watch_device, $id, $props->{"input.device"});
                $t->detach();
                last;
            }
        }

    }
    return undef;
}

sub device_removed_cb {
    my ($id) = @_;
}

sub watch_device {
    my ($id, $dev) = @_;
    say 3, "Thread watching $id ($dev) started.";
    my $fh = IO::File->new("< $dev");
    
    while ($fh) {
        my $buffer;
        my $len = sysread($fh, $buffer, $struct_len);
        last unless $len > 0;
        my ($sec, $usec, $type, $code, $value) = unpack('L!L!S!S!i!', $buffer);

        # dump events
        if ($dump_events) {
            my $lookup = $inv_codes{$type}{$code};
            if ($lookup) {
                say 1, $id, " : ", $lookup->{type}, " ", $lookup->{code}, " ", $value;
            }
        }

        # remember pressed keys for combinations
        # TODO we don't use this information yet
        if ($type eq $types{"EV_KEY"}) {
            lock %pressed_keys;
            $pressed_keys{$code}++ if ($value == 1);
            $pressed_keys{$code}-- if ($value == 0);
            # TODO somehow use the array generated by keys_pressed here
            call_handler($type, $code, $value);
        } else {
            call_handler($type, $code, $value);
        }
    }
    say 3, "Thread watching $id ($dev) terminated.";
}

sub compare_arrays {
    my ($first, $second) = @_;
    return 0 unless @$first == @$second;
    for (my $i = 0; $i < @$first; $i++) {
        return 0 if $first->[$i] ne $second->[$i];
    }
    return 1;
}

sub keys_pressed {
    my @result;
    lock %pressed_keys;
    for my $k (sort keys %pressed_keys) {
        push @result, $k if $pressed_keys{$k}
    }
    return \@result;
}

sub read_config_file {
    my ($filename) = @_;
    my $fh = IO::File->new("< $filename");

    lock(%handlers);
    %handlers = ();
    
    if ($fh) {
        while (<$fh>) {
            s/#.*//;
            next unless /[^[:space:]]/;

            if (/^((?:KEY|SW)_[[:alnum:]_]+)[[:space:]]+([[:digit:]]+)[[:space:]]+(.*)$/) {
                my ($ev, $val, $cmd) = ($1, $2, $3);
                unless (add_handler($ev, $val, $cmd)) {
                    say 1, "Unknown event $ev";
                }
            } else {
                say 1, "Unable to parse line: $_";
            }
        }
        $fh->close();
        return 1;
    } else {
        say 1, "Unable to open config file $filename";
        return 0
    }
}

sub add_handler {
    my ($event, $value, $cmd) = @_;

    my $e = $codes{$event};
    return 0 unless $e;
    
    lock %handlers;
    $handlers{$e->{type}, $e->{code}, $value} = $cmd;
    
    #$handlers{$e->{type}}{$e->{code}}{$value} = $cmd;
    # make handler shared
    #$handlers{$e->{type}} = &share({});#unless exists $handlers{$e->{type}};
    #$handlers{$e->{type}}{$e->{code}} = &share({}); #unless exists $handlers{$e->{type}}{$e->{code}};
    #$handlers{$e->{type}}{$e->{code}}{$value} = $cmd;
    return 1;
}

sub call_handler {
    my ($type, $code, $value) = @_;
    lock %handlers;
    #if ( exists $handlers{$type}{$code}{$value} && defined $handlers{$type}{$code}{$value} ) {
    if ( $handlers{$type, $code, $value} ) {
        #my $cmd = $handlers{$type}{$code}{$value};
        my $cmd = $handlers{$type, $code, $value};
        say 3, "Launching command: $cmd";
        
        if (fork() == 0) {
            exec($cmd);
        }
    }
}

sub reload_config {
    say 3, "SIGHUP received\n";
    if ($config_file) {
        lock %handlers;
        say 3, "Reloading config file $config_file\n";
        read_config_file($config_file);
    }
}

# install signal handlers
$SIG{CHLD} = "IGNORE";
$SIG{HUP} = \&reload_config;

# parse command line
my $result = GetOptions ("help|h"     => \$help,
                         "hal!"       => \$use_hal,
                         "dump"       => \$dump_events,
                         "dev|d=s"    => \@manual_devs,
                         "config|c=s" => \$config_file);

if ($help || not $result) {
    say 1, "Error parsing command line." unless $result;
    say 1, "Magmakeys ".VERSION;
    say 1, "  --help              Display this help";
    say 1, "  --config | -c       Specify config file to read hotkey definitions from";
    say 1, "  --hal               Use HAL to detect input devices (default)";
    say 1, "  --nohal             Do not use HAL but listen to specified devices";
    say 1, "  --dev | -d          Manually specify input device";
    say 1, "  --dump              Dump captured events";
    exit not $result;
}

# additional arguments not touched by Getopt are considered input devices
for my $d (@ARGV) {
    push @manual_devs, $d;
}

if (@manual_devs && $use_hal) {
    say 1, "HAL and manually specified devices are mutually exclusive.";
    exit 1;
}

if ($config_file) {
    my $success = read_config_file($config_file);

    unless ($success) {
        exit 1;
    }

} elsif (not $dump_events) {
    say 1, "A config file or --dump is required. Nothing to do, quitting.";
    exit 1;
}

if ($use_hal) {
    # we need HAL
    eval {
        use Net::DBus;
        use Net::DBus::Reactor;
    };

    my $bus = Net::DBus->system;
    say 3, "Connected to system DBUS";

    $hal = $bus->get_service("org.freedesktop.Hal");
    my $manager = $hal->get_object("/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager");
    say 3, "Connected to HAL";

    $manager->connect_to_signal("DeviceAdded", \&device_added_cb);
    $manager->connect_to_signal("DeviceRemoved", \&device_removed_cb);

    # Add all existing devices
    say 3, "Adding existing devices";
    foreach my $id (@{$manager->GetAllDevices}) {
        device_added_cb($id);
    }

    say 3, "Entering main loop";

    my $reactor = Net::DBus::Reactor->main();
    $reactor->run();
} else {
    # read device file(s) from command line
    if (@manual_devs) {
        say 3, "Spawning watcher threads...";
        my @threads = ();
        for my $d (@manual_devs) {
            push @threads, threads->create("watch_device", $d, $d);
        }
        # wait for threads to finish
        for my $t (@threads) {
            $t->join();
        }
    } else {
        say 1, "No devices specified, nothing to do.";
        exit 1;
    }
}
