Release 0.4.3-3maemo with patches to disable menus/actions, add ScrollArea and fix...
[keepassx] / src / lib / AutoTypeGlobalX11.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2008 by Tarek Saidi, Felix Geyer                   *
3  *   tarek.saidi@arcor.de                                                  *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; version 2 of the License.               *
8
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21 #include "AutoTypeGlobalX11.h"
22
23 #include "mainwindow.h"
24 #include "lib/HelperX11.h"
25 #include "dialogs/AutoTypeDlg.h"
26 #include <QX11Info>
27
28 AutoTypeGlobal* autoType = NULL;
29
30 void initAutoType(KeepassMainWindow* mainWin) {
31         autoType = new AutoTypeGlobalX11(mainWin);
32 }
33
34 AutoTypeGlobalX11::AutoTypeGlobalX11(KeepassMainWindow* mainWin) : AutoTypeX11(mainWin) {
35         wm_state = XInternAtom(dpy, "WM_STATE", true);
36         windowRoot = XRootWindow(dpy, mainWin->x11Info().screen());
37         shortcut.key = 0;
38         focusedWindow = 0;
39         oldCode = 0;
40         oldMod = 0;
41         inGlobalAutoType = false;
42         
43         //windowBlacklist << "kicker" << "KDE Desktop";
44         classBlacklist << "desktop_window" << "gnome-panel"; // Gnome
45         classBlacklist << "kdesktop" << "kicker"; // KDE 3
46         classBlacklist << "Plasma"; // KDE 4
47         classBlacklist << "xfdesktop" << "xfce4-panel"; // Xfce 4
48 }
49
50 void AutoTypeGlobalX11::updateKeymap() {
51         AutoTypeX11::updateKeymap();
52         registerGlobalShortcut(shortcut);
53 }
54
55 void AutoTypeGlobalX11::perform(IEntryHandle* entry, bool hideWindow, int nr, bool wasLocked){
56         if (inGlobalAutoType)
57                 return;
58         inGlobalAutoType = true;
59         
60         if (focusedWindow && (!hideWindow || wasLocked)) { // detect if global auto-type
61                 XSetInputFocus(dpy, focusedWindow, RevertToPointerRoot, CurrentTime);
62                 focusedWindow = 0;
63         }
64         else {
65                 focusWindow = NULL;
66         }
67         
68         AutoTypeX11::perform(entry, hideWindow, nr, wasLocked);
69         
70         inGlobalAutoType = false;
71 }
72
73 void AutoTypeGlobalX11::windowTitles(Window window, QStringList& titleList){
74         Atom type = None;
75         int format;
76         unsigned long nitems, after;
77         unsigned char* data;
78         XGetWindowProperty(dpy, window, wm_state, 0, 0, false, AnyPropertyType, &type, &format, &nitems, &after, &data);
79         if (type){
80                 XTextProperty textProp;
81                 if (XGetWMName(dpy, window, &textProp) != 0) {
82                         char** list = NULL;
83                         int count;
84                         if (Xutf8TextPropertyToTextList(dpy, &textProp, &list, &count)>=0 && list){
85                                 QString title = QString::fromUtf8(list[0]);
86                                 
87                                 QString className;
88                                 XClassHint* wmClass = XAllocClassHint();
89                                 if (XGetClassHint(dpy, window, wmClass)!=0 && wmClass->res_name!=NULL)
90                                         className = QString::fromLocal8Bit(wmClass->res_name);
91                                 XFree(wmClass);
92                                 
93                                 if (window!=windowRoot && window!=mainWin->winId() &&
94                                                 (QApplication::activeWindow()==NULL || window!=QApplication::activeWindow()->winId()) &&
95                                                 // !windowBlacklist.contains(title) &&
96                                                 (className.isNull() || !classBlacklist.contains(className))
97                                 ){
98                                         titleList.append(title);
99                                 }
100                                 XFreeStringList(list);
101                         }
102                 }
103         }
104         
105         Window root;
106         Window parent;
107         Window* children = NULL;
108         unsigned int num_children;
109         int tree = XQueryTree(dpy, window, &root, &parent, &children, &num_children);
110         if (tree && children) {
111                 for (uint i=0; i<num_children; i++)
112                         windowTitles(children[i], titleList);
113         }
114         else {
115                 XFree(children);
116         }
117 }
118  
119  QStringList AutoTypeGlobalX11::getAllWindowTitles(){
120         QStringList titleList;
121         if (wm_state) // don't do anything if WM_STATE doesn't exist
122                 windowTitles(windowRoot, titleList);
123         return titleList;
124 }
125
126 void AutoTypeGlobalX11::performGlobal(){
127         if (AutoTypeDlg::isDialogVisible()) {
128                 qWarning("Already performing auto-type, ignoring this one");
129                 return;
130         }
131         
132         focusWindow = getFocusWindow();
133         
134         bool wasLocked = mainWin->isLocked();
135         if (wasLocked)
136                 mainWin->OnUnLockWorkspace();
137         
138         if (!mainWin->isOpened())
139                 return;
140         
141         Window w;
142         int revert_to_return;
143         XGetInputFocus(dpy, &w, &revert_to_return);
144         char** list = NULL;
145         int tree;
146         do {
147                 XTextProperty textProp;
148                 if (XGetWMName(dpy, w, &textProp) != 0) {
149                         int count;
150                         if (Xutf8TextPropertyToTextList(dpy, &textProp, &list, &count)<0) return;
151                         if (list) break;
152                 }
153                 Window root = 0;
154                 Window parent = 0;
155                 Window* children = NULL;
156                 unsigned int num_children;
157                 tree = XQueryTree(dpy, w, &root, &parent, &children, &num_children);
158                 w = parent;
159                 if (children) XFree(children);
160         } while (tree && w);
161         if (!list) return;
162         QString title = QString::fromUtf8(list[0]).toLower();
163         XFreeStringList(list);
164         
165         QList<IEntryHandle*> validEntries;
166         QList<int> entryNumbers;
167         QList<IEntryHandle*> entries = mainWin->db->entries();
168         QRegExp lineMatch("Auto-Type-Window(?:-(\\d+)|):([^\\n]+)", Qt::CaseInsensitive, QRegExp::RegExp2);
169         QDateTime now = QDateTime::currentDateTime();
170         for (int i=0; i<entries.size(); i++){
171                 if ( (entries[i]->expire()!=Date_Never && entries[i]->expire()<now) ||
172                          (getRootGroupName(entries[i]).compare("backup",Qt::CaseInsensitive)==0)
173                 ){
174                         continue;
175                 }
176                 
177                 bool hasWindowEntry=false;
178                 QString comment = entries[i]->comment();
179                 int offset = 0;
180                 while ( (offset=lineMatch.indexIn(comment, offset))!=-1 ){
181                         QStringList captured = lineMatch.capturedTexts();
182                         offset += captured[0].length();
183                         int nr;
184                         QString entryWindow;
185                         bool valid;
186                         if (captured.size()==2){
187                                 nr = 0;
188                                 entryWindow = captured[1].trimmed().toLower();
189                         }
190                         else{
191                                 nr = captured[1].toInt();
192                                 entryWindow = captured[2].trimmed().toLower();
193                         }
194                         if (entryWindow.length()==0) continue;
195                         
196                         hasWindowEntry = true;
197                         bool wildStart = (entryWindow[0]=='*');
198                         bool wildEnd = (entryWindow[entryWindow.size()-1]=='*');
199                         if (wildStart&&wildEnd){
200                                 entryWindow.remove(0,1);
201                                 if (entryWindow.length()!=0){
202                                         entryWindow.remove(entryWindow.size()-1,1);
203                                         valid = title.contains(entryWindow);
204                                 }
205                                 else
206                                         valid = true;
207                         }
208                         else if (wildStart){
209                                 entryWindow.remove(0,1);
210                                 valid = title.endsWith(entryWindow);
211                         }
212                         else if (wildEnd){
213                                 entryWindow.remove(entryWindow.size()-1,1);
214                                 valid = title.startsWith(entryWindow);
215                         }
216                         else {
217                                 valid = (title==entryWindow);
218                         }
219                         
220                         if (valid){
221                                 validEntries << entries[i];
222                                 entryNumbers << nr;
223                                 break;
224                         }
225                 }
226                 
227                 if (!hasWindowEntry && config->entryTitlesMatch()){
228                         QString entryTitle = entries[i]->title().toLower();
229                         if (!entryTitle.isEmpty() && title.contains(entryTitle)){
230                                 validEntries << entries[i];
231                                 entryNumbers << 0;
232                         }
233                 }
234         }
235         
236         if (validEntries.size()==1){
237                 focusedWindow = 0;
238                 perform(validEntries[0],wasLocked,entryNumbers[0],wasLocked);
239         }
240         else if (validEntries.size()>1){
241                 focusedWindow = w;
242                 AutoTypeDlg* dlg = new AutoTypeDlg(validEntries, entryNumbers, wasLocked);
243                 dlg->show();
244         }
245 }
246
247 bool AutoTypeGlobalX11::registerGlobalShortcut(const Shortcut& s){
248         if (s.key == 0)
249                 return false;
250         
251         int code=XKeysymToKeycode(dpy, HelperX11::getKeysym(s.key));
252         uint mod=HelperX11::getShortcutModifierMask(s);
253         
254         if (s.key==shortcut.key && s.ctrl==shortcut.ctrl && s.shift==shortcut.shift && s.alt==shortcut.alt && s.altgr==shortcut.altgr && s.win==shortcut.win && code==oldCode && mod==oldMod)
255                 return true;
256         
257         HelperX11::startCatchErrors();
258         XGrabKey(dpy, code, mod, windowRoot, true, GrabModeAsync, GrabModeAsync);
259         XGrabKey(dpy, code, mod | Mod2Mask, windowRoot, true, GrabModeAsync, GrabModeAsync);
260         XGrabKey(dpy, code, mod | LockMask, windowRoot, true, GrabModeAsync, GrabModeAsync);
261         XGrabKey(dpy, code, mod | Mod2Mask | LockMask, windowRoot, true, GrabModeAsync, GrabModeAsync);
262         HelperX11::stopCatchErrors();
263         
264         if (HelperX11::errorOccurred()){
265                 XUngrabKey(dpy, code, mod, windowRoot);
266                 XUngrabKey(dpy, code, mod | Mod2Mask, windowRoot);
267                 XUngrabKey(dpy, code, mod | LockMask, windowRoot);
268                 XUngrabKey(dpy, code, mod | Mod2Mask | LockMask, windowRoot);
269                 return false;
270         }
271         else {
272                 unregisterGlobalShortcut();
273                 shortcut = s;
274                 oldCode = code;
275                 oldMod = mod;
276                 return true;
277         }
278 }
279
280 void AutoTypeGlobalX11::unregisterGlobalShortcut(){
281         if (shortcut.key==0) return;
282         
283         XUngrabKey(dpy, oldCode, oldMod, windowRoot);
284         XUngrabKey(dpy, oldCode, oldMod | Mod2Mask, windowRoot);
285         XUngrabKey(dpy, oldCode, oldMod | LockMask, windowRoot);
286         XUngrabKey(dpy, oldCode, oldMod | Mod2Mask | LockMask, windowRoot);
287         
288         shortcut.key = 0;
289         oldCode = 0;
290         oldMod = 0;
291 }
292
293 QString AutoTypeGlobalX11::getRootGroupName(IEntryHandle* entry){
294         IGroupHandle* group = entry->group();
295         int level = group->level();
296         for (int i=0; i<level; i++)
297                 group = group->parent();
298         
299         return group->title();
300 }