1 /* antimicro Gamepad to KB+M event mapper
2 * Copyright (C) 2015 Travis Nickles <nickles.travis@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <unistd.h>
19 //#include <QDebug>
20 #include <QFileInfo>
21 #include <QThreadStorage>
22
23 #include "common.h"
24
25 #include <X11/Xatom.h>
26 #include <X11/XKBlib.h>
27 #include <X11/extensions/XInput.h>
28 #include <X11/extensions/XInput2.h>
29
30 #include "x11extras.h"
31
32 const QString X11Extras::mouseDeviceName = PadderCommon::mouseDeviceName;
33 const QString X11Extras::keyboardDeviceName = PadderCommon::keyboardDeviceName;
34 const QString X11Extras::xtestMouseDeviceName = QString("Virtual core XTEST pointer");
35
36 QString X11Extras::_customDisplayString = QString("");
37
38 static QThreadStorage<X11Extras*> displays;
39
40 X11Extras* X11Extras::_instance = 0;
41
X11Extras(QObject * parent)42 X11Extras::X11Extras(QObject *parent) :
43 QObject(parent),
44 knownAliases()
45 {
46 //knownAliases = QHash<QString, QString> ();
47 _display = XOpenDisplay(NULL);
48 populateKnownAliases();
49 }
50
51 /**
52 * @brief Close display connection if one exists
53 */
~X11Extras()54 X11Extras::~X11Extras()
55 {
56 if (_display)
57 {
58 XCloseDisplay(display());
59 _display = 0;
60 //_customDisplayString = "";
61 }
62 }
63
getInstance()64 X11Extras *X11Extras::getInstance()
65 {
66 /*if (!_instance)
67 {
68 _instance = new X11Extras();
69 }
70
71 return _instance;
72 */
73 X11Extras *temp = 0;
74 if (!displays.hasLocalData())
75 {
76 temp = new X11Extras();
77 displays.setLocalData(temp);
78 }
79 else
80 {
81 temp = displays.localData();
82 }
83
84 return temp;
85 }
86
deleteInstance()87 void X11Extras::deleteInstance()
88 {
89 /*if (_instance)
90 {
91 delete _instance;
92 _instance = 0;
93 }
94 */
95 if (displays.hasLocalData())
96 {
97 X11Extras *temp = displays.localData();
98 delete temp;
99 displays.setLocalData(0);
100 }
101 }
102
103 /**
104 * @brief Get display instance
105 * @return Display struct
106 */
display()107 Display* X11Extras::display()
108 {
109 return _display;
110 }
111
hasValidDisplay()112 bool X11Extras::hasValidDisplay()
113 {
114 bool result = _display != NULL;
115 return result;
116 }
117
118 /**
119 * @brief CURRENTLY NOT USED
120 */
closeDisplay()121 void X11Extras::closeDisplay()
122 {
123 if (_display)
124 {
125 XCloseDisplay(display());
126 _display = 0;
127 //_customDisplayString = "";
128 }
129 }
130
131 /**
132 * @brief Grab instance of active display.
133 */
syncDisplay()134 void X11Extras::syncDisplay()
135 {
136 _display = XOpenDisplay(NULL);
137 //_customDisplayString = "";
138 }
139
140 /**
141 * @brief Grab instance of specified display. Useful for having the GUI
142 * on one display while generating events on another during ssh tunneling.
143 * @param Valid display string that X can use
144 */
syncDisplay(QString displayString)145 void X11Extras::syncDisplay(QString displayString)
146 {
147 QByteArray tempByteArray = displayString.toLocal8Bit();
148 _display = XOpenDisplay(tempByteArray.constData());
149 /*if (_display)
150 {
151 _customDisplayString = displayString;
152 }
153 else
154 {
155 _customDisplayString = "";
156 }
157 */
158 }
159
setCustomDisplay(QString displayString)160 void X11Extras::setCustomDisplay(QString displayString)
161 {
162 _customDisplayString = displayString;
163 }
164
165 /**
166 * @brief Return root window for a given X display
167 * @param Screen number. If no value is passed, uses screen 1.
168 * @return XID of the window
169 */
appRootWindow(int screen)170 unsigned long X11Extras::appRootWindow(int screen)
171 {
172 return screen == -1 ? XDefaultRootWindow(display()) : XRootWindowOfScreen(XScreenOfDisplay(display(), screen));
173 }
174
175 /**
176 * @brief Get appropriate alias for a known KeySym string that might be blank
177 * or contain invalid characters when returned from X.
178 * @param QString representation of a KeySym string
179 * @return Alias string or a blank QString if no alias was found
180 */
getDisplayString(QString xcodestring)181 QString X11Extras::getDisplayString(QString xcodestring)
182 {
183 QString temp;
184 if (knownAliases.contains(xcodestring))
185 {
186 temp = knownAliases.value(xcodestring);
187 }
188
189 return temp;
190 }
191
populateKnownAliases()192 void X11Extras::populateKnownAliases()
193 {
194 // These aliases are needed for xstrings that would
195 // return empty space characters from XLookupString
196 if (knownAliases.isEmpty())
197 {
198 knownAliases.insert("Escape", tr("ESC"));
199 knownAliases.insert("Tab", tr("Tab"));
200 knownAliases.insert("space", tr("Space"));
201 knownAliases.insert("Delete", tr("DEL"));
202 knownAliases.insert("Return", tr("Return"));
203 knownAliases.insert("KP_Enter", tr("KP_Enter"));
204 knownAliases.insert("BackSpace", tr("Backspace"));
205 }
206 }
207
findParentClient(Window window)208 Window X11Extras::findParentClient(Window window)
209 {
210 Window parent = 0;
211 Window root = 0;
212 Window *children = 0;
213 unsigned int num_children = 0;
214 Window finalwindow = 0;
215 Display *display = this->display();
216
217 if (windowIsViewable(display, window) &&
218 isWindowRelevant(display, window))
219 {
220 finalwindow = window;
221 }
222 else
223 {
224 bool quitTraversal = false;
225 while (!quitTraversal)
226 {
227 children = 0;
228
229 if (XQueryTree(display, window, &root, &parent, &children, &num_children))
230 {
231 if (children)
232 {
233 // must test for NULL
234 XFree(children);
235 }
236
237 if (parent)
238 {
239 if (windowIsViewable(display, parent) &&
240 isWindowRelevant(display, parent))
241 {
242 quitTraversal = true;
243 finalwindow = parent;
244 }
245 else if (parent == 0)
246 {
247 quitTraversal = true;
248 }
249 else if (parent == root)
250 {
251 quitTraversal = true;
252 }
253 else
254 {
255 window = parent;
256 }
257 }
258 else
259 {
260 quitTraversal = true;
261 }
262 }
263 else
264 {
265 quitTraversal = true;
266 }
267 }
268 }
269
270 return finalwindow;
271 }
272
273 /**
274 * @brief Check window and any parents for the window property "_NET_WM_PID"
275 * @param Window XID for window of interest
276 * @return PID of the application instance corresponding to the window
277 */
getApplicationPid(Window window)278 int X11Extras::getApplicationPid(Window window)
279 {
280 Atom atom, actual_type;
281 int actual_format = 0;
282 unsigned long nitems = 0;
283 unsigned long bytes_after = 0;
284 unsigned char *prop = 0;
285 int status = 0;
286 int pid = 0;
287 Window finalwindow = 0;
288
289 Display *display = this->display();
290 atom = XInternAtom(display, "_NET_WM_PID", True);
291 if (windowHasProperty(display, window, atom))
292 {
293 finalwindow = window;
294 }
295 else
296 {
297 Window parent = 0;
298 Window root = 0;
299 Window *children;
300 unsigned int num_children;
301 bool quitTraversal = false;
302
303 while (!quitTraversal)
304 {
305 children = 0;
306
307 if (XQueryTree(display, window, &root, &parent, &children, &num_children))
308 {
309 if (children)
310 {
311 // must test for NULL
312 XFree(children);
313 }
314
315 if (parent)
316 {
317 if (windowHasProperty(display, parent, atom))
318 {
319 quitTraversal = true;
320 finalwindow = parent;
321 }
322 else if (parent == 0)
323 {
324 quitTraversal = true;
325 }
326 else if (parent == root)
327 {
328 quitTraversal = true;
329 }
330 else
331 {
332 window = parent;
333 }
334 }
335 else
336 {
337 quitTraversal = true;
338 }
339 }
340 else
341 {
342 quitTraversal = true;
343 }
344 }
345 }
346
347 if (finalwindow)
348 {
349 status = XGetWindowProperty(display, finalwindow, atom, 0, 1024, false, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
350 if (status == 0 && prop)
351 {
352 pid = prop[1] << 8;
353 pid += prop[0];
354 XFree(prop);
355 }
356 }
357
358 return pid;
359 }
360
361 /**
362 * @brief Find the application file location for a given PID
363 * @param PID of window
364 * @return File location of application
365 */
getApplicationLocation(int pid)366 QString X11Extras::getApplicationLocation(int pid)
367 {
368 QString exepath;
369 if (pid > 0)
370 {
371 QString procString = QString("/proc/%1/exe").arg(pid);
372 QFileInfo procFileInfo(procString);
373 if (procFileInfo.exists())
374 {
375 char buf[1024];
376 QByteArray tempByteArray = procString.toLocal8Bit();
377 ssize_t len = readlink(tempByteArray.constData(), buf, sizeof(buf)-1);
378 if (len != -1)
379 {
380 buf[len] = '\0';
381 }
382
383 if (len > 0)
384 {
385 QString temp = QString::fromUtf8(buf);
386 if (!temp.isEmpty())
387 {
388 exepath = temp;
389 }
390 }
391 }
392 }
393
394 return exepath;
395 }
396
397 /**
398 * @brief Find the proper client window within a hierarchy. This check is needed
399 * in some environments where the window that has been selected is actually
400 * a child to a transparent parent window which was the one that was
401 * actually grabbed
402 * @param Top window to check
403 * @return Client window XID or 0 if no appropriate window was found
404 */
findClientWindow(Window window)405 Window X11Extras::findClientWindow(Window window)
406 {
407 Window parent = 1;
408 Window root = 0;
409 Window *children = 0;
410 unsigned int num_children = 0;
411 Window finalwindow = 0;
412 Display *display = this->display();
413
414 if (windowIsViewable(display, window) &&
415 isWindowRelevant(display, window))
416 {
417 finalwindow = window;
418 }
419 else
420 {
421 XQueryTree(display, window, &root, &parent, &children, &num_children);
422 if (children)
423 {
424 for (unsigned int i = 0; i < num_children && !finalwindow; i++)
425 {
426 if (windowIsViewable(display, children[i]) &&
427 isWindowRelevant(display, window))
428 {
429 finalwindow = children[i];
430 }
431 }
432 }
433
434 if (!finalwindow && children)
435 {
436 for (unsigned int i = 0; i < num_children && !finalwindow; i++)
437 {
438 finalwindow = findClientWindow(children[i]);
439 }
440 }
441
442 if (children)
443 {
444 XFree(children);
445 children = 0;
446 }
447 }
448
449 return finalwindow;
450 }
451
windowHasProperty(Display * display,Window window,Atom atom)452 bool X11Extras::windowHasProperty(Display *display, Window window, Atom atom)
453 {
454 bool result = false;
455
456 Atom actual_type;
457 int actual_format = 0;
458 unsigned long nitems = 0;
459 unsigned long bytes_after = 0;
460 unsigned char *prop = 0;
461 int status = 0;
462 status = XGetWindowProperty(display, window, atom, 0, 1024, false, AnyPropertyType,
463 &actual_type, &actual_format, &nitems, &bytes_after,
464 &prop);
465
466 if (status == Success && prop)
467 {
468 result = true;
469 }
470
471 if (prop)
472 {
473 XFree(prop);
474 prop = 0;
475 }
476
477 return result;
478 }
479
windowIsViewable(Display * display,Window window)480 bool X11Extras::windowIsViewable(Display *display, Window window)
481 {
482 bool result = false;
483 XWindowAttributes xwa;
484 XGetWindowAttributes(display, window, &xwa);
485 if (xwa.c_class == InputOutput && xwa.map_state == IsViewable)
486 {
487 result = true;
488 }
489
490 return result;
491 }
492
493 /**
494 * @brief Go through a window's properties and search for an Atom
495 * from a defined list. If an Atom is found in a window's properties,
496 * that window should be considered relevant and one that should be grabbed.
497 * @param Display*
498 * @param Window
499 * @return If a window has a relevant Atom in its properties.
500 */
isWindowRelevant(Display * display,Window window)501 bool X11Extras::isWindowRelevant(Display *display, Window window)
502 {
503 bool result = false;
504
505 QList<Atom> temp;
506 temp.append(XInternAtom(display, "WM_STATE", True));
507 temp.append(XInternAtom(display, "_NW_WM_STATE", True));
508 temp.append(XInternAtom(display, "_NW_WM_NAME", True));
509
510 QListIterator<Atom> iter(temp);
511 while (iter.hasNext())
512 {
513 Atom current_atom = iter.next();
514 if (windowHasProperty(display, window, current_atom))
515 {
516 iter.toBack();
517 result = true;
518 }
519 }
520
521 return result;
522 }
523
getWindowTitle(Window window)524 QString X11Extras::getWindowTitle(Window window)
525 {
526 QString temp;
527
528 Atom atom, actual_type;
529 int actual_format = 0;
530 unsigned long nitems = 0;
531 unsigned long bytes_after = 0;
532 unsigned char *prop = 0;
533 int status = 0;
534
535 //qDebug() << "WIN: 0x" << QString::number(window, 16);
536
537 Display *display = this->display();
538 Atom wm_name = XInternAtom(display, "WM_NAME", True);
539 Atom net_wm_name = XInternAtom(display, "_NET_WM_NAME", True);
540 atom = wm_name;
541
542 QList<Atom> tempList;
543 tempList.append(wm_name);
544 tempList.append(net_wm_name);
545 QListIterator<Atom> iter(tempList);
546 while (iter.hasNext())
547 {
548 Atom temp_atom = iter.next();
549 if (windowHasProperty(display, window, temp_atom))
550 {
551 iter.toBack();
552 atom = temp_atom;
553 }
554 }
555
556 status = XGetWindowProperty(display, window, atom, 0, 1024, false, AnyPropertyType,
557 &actual_type, &actual_format, &nitems, &bytes_after,
558 &prop);
559
560 if (status == Success && prop)
561 {
562 char *tempprop = (char*)prop;
563 temp.append(QString::fromUtf8(tempprop));
564 //qDebug() << temp;
565 }
566
567 if (prop)
568 {
569 XFree(prop);
570 prop = 0;
571 }
572
573 return temp;
574 }
575
getWindowClass(Window window)576 QString X11Extras::getWindowClass(Window window)
577 {
578 QString temp;
579
580 Atom atom, actual_type;
581 int actual_format = 0;
582 unsigned long nitems = 0;
583 unsigned long bytes_after = 0;
584 unsigned char *prop = 0;
585 int status = 0;
586
587 Display *display = this->display();
588 atom = XInternAtom(display, "WM_CLASS", True);
589 status = XGetWindowProperty(display, window, atom, 0, 1024, false, AnyPropertyType,
590 &actual_type, &actual_format, &nitems, &bytes_after,
591 &prop);
592
593 if (status == Success && prop)
594 {
595 //qDebug() << nitems;
596 char *null_char = strchr((char*)prop, '\0');
597 if ((char*)prop + nitems - 1 > null_char)
598 {
599 *(null_char) = ' ';
600 }
601
602 char *tempprop = (char*)prop;
603 temp.append(QString::fromUtf8(tempprop));
604 //qDebug() << temp;
605 //qDebug() << (char*)prop;
606 }
607
608 if (prop)
609 {
610 XFree(prop);
611 prop = 0;
612 }
613
614 return temp;
615 }
616
getWindowInFocus()617 unsigned long X11Extras::getWindowInFocus()
618 {
619 unsigned long result = 0;
620
621 Window currentWindow = 0;
622 int focusState = 0;
623
624 Display *display = this->display();
625 XGetInputFocus(display, ¤tWindow, &focusState);
626
627 if (currentWindow > 0)
628 {
629 result = static_cast<unsigned long>(currentWindow);
630 }
631
632 return result;
633 }
634
635 /**
636 * @brief Get QString representation of currently utilized X display.
637 * @return
638 */
getXDisplayString()639 QString X11Extras::getXDisplayString()
640 {
641 return _customDisplayString;
642 }
643
getGroup1KeySym(unsigned int virtualkey)644 unsigned int X11Extras::getGroup1KeySym(unsigned int virtualkey)
645 {
646 unsigned int result = 0;
647 Display *display = this->display();
648
649 unsigned int temp = XKeysymToKeycode(display, virtualkey);
650 result = XkbKeycodeToKeysym(display, temp, 0, 0);
651
652 return result;
653 }
654
x11ResetMouseAccelerationChange(QString pointerName)655 void X11Extras::x11ResetMouseAccelerationChange(QString pointerName)
656 {
657 int xi_opcode, event, error;
658 xi_opcode = event = error = 0;
659 Display *display = this->display();
660
661 bool result = XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error);
662 if (!result)
663 {
664 Logger::LogInfo(tr("xinput extension was not found. No mouse acceleration changes will occur."));
665 }
666 else
667 {
668 int ximajor = 2, ximinor = 0;
669 if (XIQueryVersion(display, &ximajor, &ximinor) != Success)
670 {
671 Logger::LogInfo(tr("xinput version must be at least 2.0. No mouse acceleration changes will occur."));
672 result = false;
673 }
674 }
675
676 if (result)
677 {
678 XIDeviceInfo *all_devices = 0;
679 XIDeviceInfo *current_devices = 0;
680 XIDeviceInfo *mouse_device = 0;
681
682 int num_devices = 0;
683 all_devices = XIQueryDevice(display, XIAllDevices, &num_devices);
684 for (int i=0; i < num_devices; i++)
685 {
686 current_devices = &all_devices[i];
687 if (current_devices->use == XISlavePointer &&
688 QString::fromUtf8(current_devices->name) == pointerName)
689 {
690 Logger::LogInfo(tr("Virtual pointer found with id=%1.").arg(current_devices->deviceid));
691 mouse_device = current_devices;
692 }
693 }
694
695 if (mouse_device)
696 {
697 XDevice *device = XOpenDevice(display, mouse_device->deviceid);
698
699 int num_feedbacks = 0;
700 int feedback_id = -1;
701 XFeedbackState *feedbacks = XGetFeedbackControl(display, device, &num_feedbacks);
702 XFeedbackState *temp = feedbacks;
703 for (int i=0; (i < num_feedbacks) && (feedback_id == -1); i++)
704 {
705 if (temp->c_class == PtrFeedbackClass)
706 {
707 feedback_id = temp->id;
708 }
709
710 if (i+1 < num_feedbacks)
711 {
712 temp = (XFeedbackState*) ((char*) temp + temp->length);
713 }
714 }
715
716 XFree(feedbacks);
717 feedbacks = temp = 0;
718
719 if (feedback_id <= -1)
720 {
721 Logger::LogInfo(tr("PtrFeedbackClass was not found for virtual pointer."
722 "No change to mouse acceleration will occur for device with id=%1").arg(device->device_id));
723
724 result = false;
725 }
726 else
727 {
728 Logger::LogInfo(tr("Changing mouse acceleration for device with id=%1").arg(device->device_id));
729
730 XPtrFeedbackControl feedback;
731 feedback.c_class = PtrFeedbackClass;
732 feedback.length = sizeof(XPtrFeedbackControl);
733 feedback.id = feedback_id;
734 feedback.threshold = 0;
735 feedback.accelNum = 1;
736 feedback.accelDenom = 1;
737
738 XChangeFeedbackControl(display, device, DvAccelNum|DvAccelDenom|DvThreshold,
739 (XFeedbackControl*) &feedback);
740
741 XSync(display, false);
742 }
743
744 XCloseDevice(display, device);
745 }
746
747 if (all_devices)
748 {
749 XIFreeDeviceInfo(all_devices);
750 }
751 }
752 }
753
x11ResetMouseAccelerationChange()754 void X11Extras::x11ResetMouseAccelerationChange()
755 {
756 x11ResetMouseAccelerationChange(mouseDeviceName);
757 }
758
getPointInformation(QString pointerName)759 struct X11Extras::ptrInformation X11Extras::getPointInformation(QString pointerName)
760 {
761 struct ptrInformation tempInfo;
762
763 int xi_opcode, event, error;
764 xi_opcode = event = error = 0;
765 Display *display = this->display();
766
767 bool result = XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error);
768 if (result)
769 {
770 int ximajor = 2, ximinor = 0;
771 if (XIQueryVersion(display, &ximajor, &ximinor) != Success)
772 {
773 Logger::LogInfo(tr("xinput version must be at least 2.0. No mouse acceleration changes will occur."));
774 result = false;
775 }
776 }
777
778 if (result)
779 {
780 XIDeviceInfo *all_devices = 0;
781 XIDeviceInfo *current_devices = 0;
782 XIDeviceInfo *mouse_device = 0;
783
784 int num_devices = 0;
785 all_devices = XIQueryDevice(display, XIAllDevices, &num_devices);
786 for (int i=0; i < num_devices; i++)
787 {
788 current_devices = &all_devices[i];
789 if (current_devices->use == XISlavePointer &&
790 QString::fromUtf8(current_devices->name) == pointerName)
791 {
792 mouse_device = current_devices;
793 }
794 }
795
796 if (mouse_device)
797 {
798 XDevice *device = XOpenDevice(display, mouse_device->deviceid);
799
800 int num_feedbacks = 0;
801 int feedback_id = -1;
802 XFeedbackState *feedbacks = XGetFeedbackControl(display, device, &num_feedbacks);
803 XFeedbackState *temp = feedbacks;
804 for (int i=0; (i < num_feedbacks) && (feedback_id == -1); i++)
805 {
806 if (temp->c_class == PtrFeedbackClass)
807 {
808 feedback_id = temp->id;
809 }
810
811 if (feedback_id == -1 && (i+1 < num_feedbacks))
812 {
813 temp = (XFeedbackState*) ((char*) temp + temp->length);
814 }
815 }
816
817 if (feedback_id <= -1)
818 {
819 result = false;
820 }
821 else
822 {
823 XPtrFeedbackState *tempPtrFeedback = reinterpret_cast<XPtrFeedbackState*>(temp);
824 tempInfo.id = feedback_id;
825 tempInfo.accelNum = tempPtrFeedback->accelNum;
826 tempInfo.accelDenom = tempPtrFeedback->accelDenom;
827 tempInfo.threshold = tempPtrFeedback->threshold;
828 }
829
830 XFree(feedbacks);
831 feedbacks = temp = 0;
832 XCloseDevice(display, device);
833 }
834
835 if (all_devices)
836 {
837 XIFreeDeviceInfo(all_devices);
838 }
839 }
840
841 return tempInfo;
842 }
843
getPointInformation()844 struct X11Extras::ptrInformation X11Extras::getPointInformation()
845 {
846 return getPointInformation(mouseDeviceName);
847 }
848
getPos()849 QPoint X11Extras::getPos()
850 {
851 XEvent mouseEvent;
852 Window wid = DefaultRootWindow(display());
853 XWindowAttributes xwAttr;
854
855 XQueryPointer(display(), wid,
856 &mouseEvent.xbutton.root, &mouseEvent.xbutton.window,
857 &mouseEvent.xbutton.x_root, &mouseEvent.xbutton.y_root,
858 &mouseEvent.xbutton.x, &mouseEvent.xbutton.y,
859 &mouseEvent.xbutton.state);
860
861 XGetWindowAttributes(display(), wid, &xwAttr);
862 QPoint currentPoint(mouseEvent.xbutton.x_root, mouseEvent.xbutton.y_root);
863 return currentPoint;
864 }
865