/*************************************************************************** * Copyright (C) 2005-2008 by Tarek Saidi, Felix Geyer * * tarek.saidi@arcor.de * * * * 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; version 2 of the License. * * * * 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. * ***************************************************************************/ #include "AutoTypeX11.h" #include "mainwindow.h" #include "lib/HelperX11.h" #include #ifndef GLOBAL_AUTOTYPE AutoType* autoType = NULL; void initAutoType(KeepassMainWindow* mainWin) { autoType = new AutoTypeX11(mainWin); } #endif AutoTypeAction::AutoTypeAction(AutoTypeActionType t, quint16 d) : type(t), data(d){ } bool AutoTypeX11::error_detected = false; AutoTypeX11::AutoTypeX11(KeepassMainWindow* mainWin) { this->mainWin = mainWin; dpy = mainWin->x11Info().display(); keysym_table = NULL; alt_mask = 0; meta_mask = 0; altgr_mask = 0; altgr_keysym = NoSymbol; focused_window = None; focused_subwindow = None; ReadKeymap(); if (!altgr_mask) AddModifier(XK_Mode_switch); } void AutoTypeX11::perform(IEntryHandle* entry, bool hideWindow, int nr, bool wasLocked){ QString indexStr; if (nr==0) indexStr = "Auto-Type:"; else indexStr = QString("Auto-Type-%1:").arg(nr); QString str; QString comment=entry->comment(); int c=comment.count(indexStr, Qt::CaseInsensitive); if(c>1) { qWarning("More than one 'Auto-Type:' key sequence found.\nAllowed is only one per entry."); return; } else if (c==1) { int start = comment.indexOf(indexStr,0,Qt::CaseInsensitive) + indexStr.length(); int end = comment.indexOf("\n", start); if (end == -1) end = comment.length(); str=comment.mid(start,end-start).trimmed(); if (str.isEmpty()) return; } else { bool usernameEmpty = entry->username().trimmed().isEmpty(); SecString password=entry->password(); password.unlock(); bool passwordEmpty = password.string().trimmed().isEmpty(); if (usernameEmpty && passwordEmpty) return; else if (usernameEmpty) str="{PASSWORD}{ENTER}"; else if (passwordEmpty) str="{USERNAME}{ENTER}"; else str="{USERNAME}{TAB}{PASSWORD}{ENTER}"; } QList Keys; for(int i=0;i=str.size()){ qWarning("Syntax Error in Auto-Type sequence near character %d\nFound '{' without closing '}'", i+10); return; } templateToKeysyms(tmpl.toLower(),Keys,entry); continue; } else{ Keys << AutoTypeAction(TypeKey, str[i].unicode()); } } if (hideWindow) mainWin->hide(); QApplication::processEvents(); sleepTime(config->autoTypePreGap()); QString type; for(int i=0;ilockOnMinimize()){ if (hideWindow || wasLocked){ if ( !(config->showSysTrayIcon() && config->minimizeTray()) ) mainWin->showMinimized(); else mainWin->OnUnLockWorkspace(); } } else{ if (hideWindow && !(config->showSysTrayIcon() && config->minimizeTray()) ) mainWin->showMinimized(); if (wasLocked) mainWin->OnUnLockWorkspace(); } } void AutoTypeX11::sleepTime(int msec){ if (msec==0) return; timespec timeOut, remains; timeOut.tv_sec = msec/1000; timeOut.tv_nsec = (msec%1000)*1000000; nanosleep(&timeOut, &remains); } void AutoTypeX11::templateToKeysyms(const QString& tmpl, QList& keys,IEntryHandle* entry){ //tmpl must be lower case!!! if(!tmpl.compare("title")){ stringToKeysyms(entry->title(),keys); return; } if(!tmpl.compare("username")){ stringToKeysyms(entry->username(),keys); return; } if(!tmpl.compare("url")){ stringToKeysyms(entry->url(),keys); return; } if(!tmpl.compare("password")){ SecString password=entry->password(); password.unlock(); stringToKeysyms(password,keys); return; } if(!tmpl.compare("space")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym(' ')); return; } if(!tmpl.compare("backspace") || !tmpl.compare("bs") || !tmpl.compare("bksp")){ keys << AutoTypeAction(TypeKey, XK_BackSpace); return; } if(!tmpl.compare("break")){ keys << AutoTypeAction(TypeKey, XK_Break); return; } if(!tmpl.compare("capslock")){ keys << AutoTypeAction(TypeKey, XK_Caps_Lock); return; } if(!tmpl.compare("del") || !tmpl.compare("delete")){ keys << AutoTypeAction(TypeKey, XK_Delete); return; } if(!tmpl.compare("end")){ keys << AutoTypeAction(TypeKey, XK_End); return; } if(!tmpl.compare("enter")){ keys << AutoTypeAction(TypeKey, XK_Return); return; } if(!tmpl.compare("esc")){ keys << AutoTypeAction(TypeKey, XK_Escape); return; } if(!tmpl.compare("help")){ keys << AutoTypeAction(TypeKey, XK_Help); return; } if(!tmpl.compare("home")){ keys << AutoTypeAction(TypeKey, XK_Home); return; } if(!tmpl.compare("insert") || !tmpl.compare("ins")){ keys << AutoTypeAction(TypeKey, XK_Insert); return; } if(!tmpl.compare("numlock")){ keys << AutoTypeAction(TypeKey, XK_Num_Lock); return; } if(!tmpl.compare("scroll")){ keys << AutoTypeAction(TypeKey, XK_Scroll_Lock); return; } if(!tmpl.compare("pgdn")){ keys << AutoTypeAction(TypeKey, XK_Page_Down); return; } if(!tmpl.compare("pgup")){ keys << AutoTypeAction(TypeKey, XK_Page_Up); return; } if(!tmpl.compare("prtsc")){ keys << AutoTypeAction(TypeKey, XK_3270_PrintScreen); return; } if(!tmpl.compare("up")){ keys << AutoTypeAction(TypeKey, XK_Up); return; } if(!tmpl.compare("down")){ keys << AutoTypeAction(TypeKey, XK_Down); return; } if(!tmpl.compare("left")){ keys << AutoTypeAction(TypeKey, XK_Left); return; } if(!tmpl.compare("right")){ keys << AutoTypeAction(TypeKey, XK_Right); return; } if(!tmpl.compare("f1")){ keys << AutoTypeAction(TypeKey, XK_F1); return; } if(!tmpl.compare("f2")){ keys << AutoTypeAction(TypeKey, XK_F2); return; } if(!tmpl.compare("f3")){ keys << AutoTypeAction(TypeKey, XK_F3); return; } if(!tmpl.compare("f4")){ keys << AutoTypeAction(TypeKey, XK_F4); return; } if(!tmpl.compare("f5")){ keys << AutoTypeAction(TypeKey, XK_F5); return; } if(!tmpl.compare("f6")){ keys << AutoTypeAction(TypeKey, XK_F6); return; } if(!tmpl.compare("f7")){ keys << AutoTypeAction(TypeKey, XK_F7); return; } if(!tmpl.compare("f8")){ keys << AutoTypeAction(TypeKey, XK_F8); return; } if(!tmpl.compare("f9")){ keys << AutoTypeAction(TypeKey, XK_F9); return; } if(!tmpl.compare("f10")){ keys << AutoTypeAction(TypeKey, XK_F10); return; } if(!tmpl.compare("f11")){ keys << AutoTypeAction(TypeKey, XK_F11); return; } if(!tmpl.compare("f12")){ keys << AutoTypeAction(TypeKey, XK_F12); return; } if(!tmpl.compare("f13")){ keys << AutoTypeAction(TypeKey, XK_F13); return; } if(!tmpl.compare("f14")){ keys << AutoTypeAction(TypeKey, XK_F14); return; } if(!tmpl.compare("f15")){ keys << AutoTypeAction(TypeKey, XK_F15); return; } if(!tmpl.compare("f16")){ keys << AutoTypeAction(TypeKey, XK_F16); return; } if(!tmpl.compare("add") || !tmpl.compare("plus")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('+')); return; } if(!tmpl.compare("subtract")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('-')); return; } if(!tmpl.compare("multiply")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('+')); return; } if(!tmpl.compare("divide")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('/')); return; } if(!tmpl.compare("at")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('@')); return; } if(!tmpl.compare("percent")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('%')); return; } if(!tmpl.compare("caret")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('^')); return; } if(!tmpl.compare("tilde")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('~')); return; } if(!tmpl.compare("leftbrace")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('{')); return; } if(!tmpl.compare("rightbrace")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('}')); return; } if(!tmpl.compare("leftparen")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym('(')); return; } if(!tmpl.compare("rightparen")){ keys << AutoTypeAction(TypeKey, HelperX11::getKeysym(')')); return; } if(!tmpl.compare("winl")){ keys << AutoTypeAction(TypeKey, XK_Super_L); return; } if(!tmpl.compare("winr")){ keys << AutoTypeAction(TypeKey, XK_Super_R); return; } if(!tmpl.compare("win")){ keys << AutoTypeAction(TypeKey, XK_Super_L); return; } if(!tmpl.compare("tab")){ keys << AutoTypeAction(TypeKey, XK_Tab); return; } if(tmpl.startsWith("delay ") && tmpl.length()>6){ bool ok; quint16 delay = tmpl.right(tmpl.length()-6).toInt(&ok); if (ok && delay>0 && delay<=10000) keys << AutoTypeAction(Delay, delay); } } void AutoTypeX11::stringToKeysyms(const QString& string,QList& KeySymList){ for(int i=0; i * http://homepage3.nifty.com/tsato/ * * 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 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. */ /* * Insert a specified keysym to unused position in the keymap table. * This will be called to add required keysyms on-the-fly. * if the second parameter is TRUE, the keysym will be added to the * non-shifted position - this may be required for modifier keys * (e.g. Mode_switch) and some special keys (e.g. F20). */ int AutoTypeX11::AddKeysym(KeySym keysym, bool top) { int keycode, pos, max_pos, inx, phase; if (top) { max_pos = 0; } else { max_pos = keysym_per_keycode - 1; if (4 <= max_pos) max_pos = 3; if (2 <= max_pos && altgr_keysym != XK_Mode_switch) max_pos = 1; } for (phase = 0; phase < 2; phase++) { for (keycode = max_keycode; min_keycode <= keycode; keycode--) { for (pos = max_pos; 0 <= pos; pos--) { inx = (keycode - min_keycode) * keysym_per_keycode; if ((phase != 0 || keysym_table[inx] == NoSymbol) && keysym_table[inx] < 0xFF00) { /* In the first phase, to avoid modifing existing keys, */ /* add the keysym only to the keys which has no keysym in the first position. */ /* If no place fuond in the first phase, add the keysym for any keys except */ /* for modifier keys and other special keys */ if (keysym_table[inx + pos] == NoSymbol) { keysym_table[inx + pos] = keysym; XChangeKeyboardMapping(dpy, keycode, keysym_per_keycode, &keysym_table[inx], 1); XFlush(dpy); return keycode; } } } } } qWarning("Couldn't add \"%s\" to keymap", XKeysymToString(keysym)); return NoSymbol; } /* * Add the specified key as a new modifier. * This is used to use Mode_switch (AltGr) as a modifier. */ void AutoTypeX11::AddModifier(KeySym keysym) { XModifierKeymap *modifiers; int keycode, i, pos; keycode = XKeysymToKeycode(dpy, keysym); if (keycode == NoSymbol) keycode = AddKeysym(keysym, TRUE); modifiers = XGetModifierMapping(dpy); for (i = 7; 3 < i; i--) { if (modifiers->modifiermap[i * modifiers->max_keypermod] == NoSymbol || ((keysym_table[(modifiers->modifiermap[i * modifiers->max_keypermod] - min_keycode) * keysym_per_keycode]) == XK_ISO_Level3_Shift && keysym == XK_Mode_switch)) { for (pos = 0; pos < modifiers->max_keypermod; pos++) { if (modifiers->modifiermap[i * modifiers->max_keypermod + pos] == NoSymbol) { modifiers->modifiermap[i * modifiers->max_keypermod + pos] = keycode; XSetModifierMapping(dpy, modifiers); return; } } } } qWarning("Couldn't add \"%s\" as modifier", XKeysymToString(keysym)); } /* * Read keyboard mapping and modifier mapping. * Keyboard mapping is used to know what keys are in shifted position. * Modifier mapping is required because we should know Alt and Meta * key are used as which modifier. */ void AutoTypeX11::ReadKeymap() { int i; int keycode, inx, pos; KeySym keysym; XModifierKeymap *modifiers; int last_altgr_mask; XDisplayKeycodes(dpy, &min_keycode, &max_keycode); if (keysym_table != NULL) XFree(keysym_table); keysym_table = XGetKeyboardMapping(dpy, min_keycode, max_keycode - min_keycode + 1, &keysym_per_keycode); for (keycode = min_keycode; keycode <= max_keycode; keycode++) { /* if the first keysym is alphabet and the second keysym is NoSymbol, it is equivalent to pair of lowercase and uppercase alphabet */ inx = (keycode - min_keycode) * keysym_per_keycode; if (keysym_table[inx + 1] == NoSymbol && ((XK_A <= keysym_table[inx] && keysym_table[inx] <= XK_Z) || (XK_a <= keysym_table[inx] && keysym_table[inx] <= XK_z))) { if (XK_A <= keysym_table[inx] && keysym_table[inx] <= XK_Z) keysym_table[inx] = keysym_table[inx] - XK_A + XK_a; keysym_table[inx + 1] = keysym_table[inx] - XK_a + XK_A; } } last_altgr_mask = altgr_mask; alt_mask = 0; meta_mask = 0; altgr_mask = 0; altgr_keysym = NoSymbol; modifiers = XGetModifierMapping(dpy); for (i = 0; i < 8; i++) { for (pos = 0; pos < modifiers->max_keypermod; pos++) { keycode = modifiers->modifiermap[i * modifiers->max_keypermod + pos]; if (keycode < min_keycode || max_keycode < keycode) continue; keysym = keysym_table[(keycode - min_keycode) * keysym_per_keycode]; if (keysym == XK_Alt_L || keysym == XK_Alt_R) { alt_mask = 1 << i; } else if (keysym == XK_Meta_L || keysym == XK_Meta_R) { meta_mask = 1 << i; } else if (keysym == XK_Mode_switch) { if (altgr_keysym == XK_ISO_Level3_Shift) { } else { altgr_mask = 0x0101 << i; /* I don't know why, but 0x2000 was required for mod3 on my Linux box */ altgr_keysym = keysym; } } else if (keysym == XK_ISO_Level3_Shift) { /* if no Mode_switch, try to use ISO_Level3_Shift instead */ /* however, it may not work as intended - I don't know why */ altgr_mask = 1 << i; altgr_keysym = keysym; } } } XFreeModifiermap(modifiers); } /* * Send event to the focused window. * If input focus is specified explicitly, select the window * before send event to the window. */ void AutoTypeX11::SendEvent(XKeyEvent *event) { static bool first = TRUE; XSync(event->display, FALSE); int (*oldHandler) (Display*, XErrorEvent*) = XSetErrorHandler(MyErrorHandler); error_detected = FALSE; if (focused_window != None) { /* set input focus if input focus is set explicitly */ XSetInputFocus(event->display, focused_window, RevertToParent, CurrentTime); XSync(event->display, FALSE); } if (!error_detected) { Window root, child, w; int root_x, root_y, x, y; unsigned int mask; int revert_to; w = None; first = FALSE; w = focused_subwindow; if (w == None) XGetInputFocus(event->display, &w, &revert_to); if (w != None) { XQueryPointer(event->display, w, &root, &child, &root_x, &root_y, &x, &y, &mask); XWarpPointer(event->display, None, w, 0, 0, 0, 0, 1, 1); XFlush(event->display); } XTestFakeKeyEvent(event->display, event->keycode, event->type == KeyPress, 0); XFlush(event->display); if (w != None) { XWarpPointer(event->display, None, root, 0, 0, 0, 0, root_x, root_y); XFlush(event->display); } } else { XTestFakeKeyEvent(event->display, event->keycode, event->type == KeyPress, 0); XFlush(event->display); } if (error_detected) { /* reset focus because focused window is (probably) no longer exist */ focused_window = None; focused_subwindow = None; } XSetErrorHandler(oldHandler); } /* * Send sequence of KeyPressed/KeyReleased events to the focused * window to simulate keyboard. If modifiers (shift, control, etc) * are set ON, many events will be sent. */ void AutoTypeX11::SendKeyPressedEvent(KeySym keysym, unsigned int shift) { Window cur_focus; int revert_to; XKeyEvent event; int keycode; int phase, inx; bool found; if (focused_subwindow != None) cur_focus = focused_subwindow; else XGetInputFocus(dpy, &cur_focus, &revert_to); found = FALSE; keycode = 0; if (keysym != NoSymbol) { for (phase = 0; phase < 2; phase++) { for (keycode = min_keycode; !found && (keycode <= max_keycode); keycode++) { /* Determine keycode for the keysym: we use this instead of XKeysymToKeycode() because we must know shift_state, too */ inx = (keycode - min_keycode) * keysym_per_keycode; if (keysym_table[inx] == keysym) { shift &= ~altgr_mask; if (keysym_table[inx + 1] != NoSymbol) shift &= ~ShiftMask; found = TRUE; break; } else if (keysym_table[inx + 1] == keysym) { shift &= ~altgr_mask; shift |= ShiftMask; found = TRUE; break; } } if (!found && altgr_mask && 3 <= keysym_per_keycode) { for (keycode = min_keycode; !found && (keycode <= max_keycode); keycode++) { inx = (keycode - min_keycode) * keysym_per_keycode; if (keysym_table[inx + 2] == keysym) { shift &= ~ShiftMask; shift |= altgr_mask; found = TRUE; break; } else if (4 <= keysym_per_keycode && keysym_table[inx + 3] == keysym) { shift |= ShiftMask | altgr_mask; found = TRUE; break; } } } if (found) break; if (0xF000 <= keysym) { /* for special keys such as function keys, first try to add it in the non-shifted position of the keymap */ if (AddKeysym(keysym, TRUE) == NoSymbol) AddKeysym(keysym, FALSE); } else { AddKeysym(keysym, FALSE); } } } event.display = dpy; event.window = cur_focus; event.root = RootWindow(event.display, DefaultScreen(event.display)); event.subwindow = None; event.time = CurrentTime; event.x = 1; event.y = 1; event.x_root = 1; event.y_root = 1; event.same_screen = TRUE; Window root, child; int root_x, root_y, x, y; unsigned int mask; XQueryPointer(dpy, event.root, &root, &child, &root_x, &root_y, &x, &y, &mask); event.type = KeyRelease; event.state = 0; if (mask & ControlMask) { event.keycode = XKeysymToKeycode(dpy, XK_Control_L); SendEvent(&event); } if (mask & alt_mask) { event.keycode = XKeysymToKeycode(dpy, XK_Alt_L); SendEvent(&event); } if (mask & meta_mask) { event.keycode = XKeysymToKeycode(dpy, XK_Meta_L); SendEvent(&event); } if (mask & altgr_mask) { event.keycode = XKeysymToKeycode(dpy, altgr_keysym); SendEvent(&event); } if (mask & ShiftMask) { event.keycode = XKeysymToKeycode(dpy, XK_Shift_L); SendEvent(&event); } if (mask & LockMask) { event.keycode = XKeysymToKeycode(dpy, XK_Caps_Lock); SendEvent(&event); } event.type = KeyPress; event.state = 0; if (shift & ControlMask) { event.keycode = XKeysymToKeycode(dpy, XK_Control_L); SendEvent(&event); event.state |= ControlMask; } if (shift & alt_mask) { event.keycode = XKeysymToKeycode(dpy, XK_Alt_L); SendEvent(&event); event.state |= alt_mask; } if (shift & meta_mask) { event.keycode = XKeysymToKeycode(dpy, XK_Meta_L); SendEvent(&event); event.state |= meta_mask; } if (shift & altgr_mask) { event.keycode = XKeysymToKeycode(dpy, altgr_keysym); SendEvent(&event); event.state |= altgr_mask; } if (shift & ShiftMask) { event.keycode = XKeysymToKeycode(dpy, XK_Shift_L); SendEvent(&event); event.state |= ShiftMask; } if (keysym != NoSymbol) { /* send event for the key itself */ event.keycode = found ? keycode : XKeysymToKeycode(dpy, keysym); if (event.keycode == NoSymbol) { if ((keysym & ~0x7f) == 0 && isprint(keysym)) qWarning("No such key: %c", (char)keysym); else if (XKeysymToString(keysym) != NULL) qWarning("No such key: keysym=%s (0x%lX)", XKeysymToString(keysym), (long)keysym); else qWarning("No such key: keysym=0x%lX", (long)keysym); } else { SendEvent(&event); event.type = KeyRelease; SendEvent(&event); } } event.type = KeyRelease; if (shift & ShiftMask) { event.keycode = XKeysymToKeycode(dpy, XK_Shift_L); SendEvent(&event); event.state &= ~ShiftMask; } if (shift & altgr_mask) { event.keycode = XKeysymToKeycode(dpy, altgr_keysym); SendEvent(&event); event.state &= ~altgr_mask; } if (shift & meta_mask) { event.keycode = XKeysymToKeycode(dpy, XK_Meta_L); SendEvent(&event); event.state &= ~meta_mask; } if (shift & alt_mask) { event.keycode = XKeysymToKeycode(dpy, XK_Alt_L); SendEvent(&event); event.state &= ~alt_mask; } if (shift & ControlMask) { event.keycode = XKeysymToKeycode(dpy, XK_Control_L); SendEvent(&event); event.state &= ~ControlMask; } } int AutoTypeX11::MyErrorHandler(Display *my_dpy, XErrorEvent *event) { char msg[200]; error_detected = TRUE; if (event->error_code == BadWindow) { return 0; } XGetErrorText(my_dpy, event->error_code, msg, sizeof(msg) - 1); qWarning("X error trapped: %s, request-code=%d\n", msg, event->request_code); return 0; }