Changeset View
Changeset View
Standalone View
Standalone View
kcmkwin/kwinrules/detectwidget.cpp
Show All 25 Lines | |||||
26 | #include <QRadioButton> | 26 | #include <QRadioButton> | ||
27 | #include <QCheckBox> | 27 | #include <QCheckBox> | ||
28 | #include <QDialogButtonBox> | 28 | #include <QDialogButtonBox> | ||
29 | #include <QMouseEvent> | 29 | #include <QMouseEvent> | ||
30 | #include <QEvent> | 30 | #include <QEvent> | ||
31 | #include <QByteArray> | 31 | #include <QByteArray> | ||
32 | #include <QTimer> | 32 | #include <QTimer> | ||
33 | 33 | | |||
34 | #include <X11/Xlib.h> | 34 | #include <QDBusConnection> | ||
35 | #include <X11/Xatom.h> | 35 | #include <QDBusMessage> | ||
36 | #include <X11/Xutil.h> | 36 | #include <QDBusPendingCallWatcher> | ||
37 | #include <fixx11h.h> | 37 | #include <QDBusPendingReply> | ||
38 | #include <QX11Info> | 38 | | ||
39 | Q_DECLARE_METATYPE(NET::WindowType) | ||||
39 | 40 | | |||
40 | namespace KWin | 41 | namespace KWin | ||
41 | { | 42 | { | ||
42 | 43 | | |||
43 | DetectWidget::DetectWidget(QWidget* parent) | 44 | DetectWidget::DetectWidget(QWidget* parent) | ||
44 | : QWidget(parent) | 45 | : QWidget(parent) | ||
45 | { | 46 | { | ||
46 | setupUi(this); | 47 | setupUi(this); | ||
47 | } | 48 | } | ||
48 | 49 | | |||
49 | DetectDialog::DetectDialog(QWidget* parent, const char* name) | 50 | DetectDialog::DetectDialog(QWidget* parent, const char* name) | ||
50 | : QDialog(parent), | 51 | : QDialog(parent) | ||
51 | grabber() | | |||
52 | { | 52 | { | ||
53 | setObjectName(name); | 53 | setObjectName(name); | ||
54 | setModal(true); | 54 | setModal(true); | ||
55 | setLayout(new QVBoxLayout); | 55 | setLayout(new QVBoxLayout); | ||
56 | 56 | | |||
57 | widget = new DetectWidget(this); | 57 | widget = new DetectWidget(this); | ||
58 | layout()->addWidget(widget); | 58 | layout()->addWidget(widget); | ||
59 | 59 | | |||
60 | QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this); | 60 | QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this); | ||
61 | layout()->addWidget(buttons); | 61 | layout()->addWidget(buttons); | ||
62 | 62 | | |||
63 | connect(buttons, SIGNAL(accepted()), SLOT(accept())); | 63 | connect(buttons, SIGNAL(accepted()), SLOT(accept())); | ||
64 | connect(buttons, SIGNAL(rejected()), SLOT(reject())); | 64 | connect(buttons, SIGNAL(rejected()), SLOT(reject())); | ||
65 | } | 65 | } | ||
66 | 66 | | |||
67 | void DetectDialog::detect(WId window, int secs) | 67 | void DetectDialog::detect(int secs) | ||
68 | { | 68 | { | ||
69 | if (window == 0) | | |||
70 | QTimer::singleShot(secs*1000, this, SLOT(selectWindow())); | 69 | QTimer::singleShot(secs*1000, this, SLOT(selectWindow())); | ||
71 | else | | |||
72 | readWindow(window); | | |||
73 | } | | |||
74 | | ||||
75 | void DetectDialog::readWindow(WId w) | | |||
76 | { | | |||
77 | if (w == 0) { | | |||
78 | emit detectionDone(false); | | |||
79 | return; | | |||
80 | } | | |||
81 | info.reset(new KWindowInfo(w, NET::WMAllProperties, NET::WM2AllProperties)); // read everything | | |||
82 | if (!info->valid()) { | | |||
83 | emit detectionDone(false); | | |||
84 | return; | | |||
85 | } | | |||
86 | wmclass_class = info->windowClassClass(); | | |||
87 | wmclass_name = info->windowClassName(); | | |||
88 | role = info->windowRole(); | | |||
89 | type = info->windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | | |||
90 | | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | | |||
91 | | NET::UtilityMask | NET::SplashMask); | | |||
92 | title = info->name(); | | |||
93 | machine = info->clientMachine(); | | |||
94 | executeDialog(); | | |||
95 | } | 70 | } | ||
96 | 71 | | |||
97 | void DetectDialog::executeDialog() | 72 | void DetectDialog::executeDialog() | ||
98 | { | 73 | { | ||
99 | static const char* const types[] = { | 74 | static const char* const types[] = { | ||
100 | I18N_NOOP("Normal Window"), | 75 | I18N_NOOP("Normal Window"), | ||
101 | I18N_NOOP("Desktop"), | 76 | I18N_NOOP("Desktop"), | ||
102 | I18N_NOOP("Dock (panel)"), | 77 | I18N_NOOP("Dock (panel)"), | ||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Line(s) | |||||
165 | 140 | | |||
166 | QByteArray DetectDialog::selectedMachine() const | 141 | QByteArray DetectDialog::selectedMachine() const | ||
167 | { | 142 | { | ||
168 | return machine; | 143 | return machine; | ||
169 | } | 144 | } | ||
170 | 145 | | |||
171 | void DetectDialog::selectWindow() | 146 | void DetectDialog::selectWindow() | ||
172 | { | 147 | { | ||
173 | if (!KWin::Cursor::self()) { | 148 | QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), | ||
174 | qApp->setProperty("x11Connection", QVariant::fromValue<void*>(QX11Info::connection())); | 149 | QStringLiteral("/KWin"), | ||
175 | qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); | 150 | QStringLiteral("org.kde.KWin"), | ||
176 | new X11Cursor(this); | 151 | QStringLiteral("queryWindowInfo")); | ||
177 | } | 152 | QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message); | ||
davidedmundson: I would explicilty specify a massive timeout for anything which has user interaction the other… | |||||
178 | // use a dialog, so that all user input is blocked | 153 | | ||
179 | // use WX11BypassWM and moving away so that it's not actually visible | 154 | QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); | ||
180 | // grab only mouse, so that keyboard can be used e.g. for switching windows | 155 | connect(callWatcher, &QDBusPendingCallWatcher::finished, this, | ||
181 | grabber.reset(new QDialog(nullptr, Qt::X11BypassWindowManagerHint)); | 156 | [this](QDBusPendingCallWatcher *self) { | ||
182 | grabber->move(-1000, -1000); | 157 | QDBusPendingReply<QVariantMap> reply = *self; | ||
183 | grabber->setModal(true); | 158 | self->deleteLater(); | ||
184 | grabber->show(); | 159 | if (!reply.isValid()) { | ||
185 | // Qt uses QX11Info::appTime() to grab the pointer, what can silently fail (#318437) ... | | |||
186 | XSync(QX11Info::display(), false); | | |||
187 | if (XGrabPointer(QX11Info::display(), grabber->winId(), false, ButtonReleaseMask, | | |||
188 | GrabModeAsync, GrabModeAsync, None, KWin::Cursor::x11Cursor(Qt::CrossCursor), | | |||
189 | CurrentTime) == Success) { // ...so we use the far more convincing CurrentTime | | |||
190 | QCoreApplication::instance()->installNativeEventFilter(this); | | |||
191 | } else { | | |||
192 | // ... and if we fail, cleanup, so we won't receive random events | | |||
193 | grabber.reset(); | | |||
194 | } | | |||
195 | } | | |||
196 | | ||||
197 | bool DetectDialog::nativeEventFilter(const QByteArray &eventType, void *message, long int*) | | |||
198 | { | | |||
199 | if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { | | |||
200 | return false; | | |||
201 | } | | |||
202 | auto *event = reinterpret_cast<xcb_generic_event_t *>(message); | | |||
203 | if ((event->response_type & ~0x80) != XCB_BUTTON_RELEASE) { | | |||
204 | return false; | | |||
205 | } | | |||
206 | QCoreApplication::instance()->removeNativeEventFilter(this); | | |||
207 | grabber.reset(); | | |||
208 | auto *me = reinterpret_cast<xcb_button_press_event_t*>(event); | | |||
209 | if (me->detail != XCB_BUTTON_INDEX_1) { | | |||
210 | emit detectionDone(false); | 160 | emit detectionDone(false); | ||
211 | return true; | 161 | return; | ||
212 | } | | |||
213 | readWindow(findWindow()); | | |||
214 | return true; | | |||
215 | } | | |||
216 | | ||||
217 | WId DetectDialog::findWindow() | | |||
218 | { | | |||
219 | Window root; | | |||
220 | Window child; | | |||
221 | uint mask; | | |||
222 | int rootX, rootY, x, y; | | |||
223 | Window parent = QX11Info::appRootWindow(); | | |||
224 | Atom wm_state = XInternAtom(QX11Info::display(), "WM_STATE", False); | | |||
225 | for (int i = 0; | | |||
226 | i < 10; | | |||
227 | ++i) { | | |||
228 | XQueryPointer(QX11Info::display(), parent, &root, &child, | | |||
229 | &rootX, &rootY, &x, &y, &mask); | | |||
230 | if (child == None) | | |||
231 | return 0; | | |||
232 | Atom type; | | |||
233 | int format; | | |||
234 | unsigned long nitems, after; | | |||
235 | unsigned char* prop; | | |||
236 | if (XGetWindowProperty(QX11Info::display(), child, wm_state, 0, 0, False, AnyPropertyType, | | |||
237 | &type, &format, &nitems, &after, &prop) == Success) { | | |||
238 | if (prop != nullptr) | | |||
239 | XFree(prop); | | |||
240 | if (type != None) | | |||
241 | return child; | | |||
242 | } | 162 | } | ||
243 | parent = child; | 163 | m_windowInfo = reply.value(); | ||
164 | wmclass_class = m_windowInfo.value("resourceClass").toByteArray(); | ||||
165 | wmclass_name = m_windowInfo.value("resourceName").toByteArray(); | ||||
166 | role = m_windowInfo.value("role").toByteArray(); | ||||
167 | type = m_windowInfo.value("type").value<NET::WindowType>(); | ||||
168 | title = m_windowInfo.value("caption").toString(); | ||||
169 | machine = m_windowInfo.value("clientMachine").toByteArray(); | ||||
170 | executeDialog(); | ||||
244 | } | 171 | } | ||
245 | return 0; | 172 | ); | ||
246 | } | 173 | } | ||
247 | 174 | | |||
248 | } // namespace | 175 | } // namespace | ||
249 | 176 | |
I would explicilty specify a massive timeout for anything which has user interaction the other side.