You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
7.4 KiB
249 lines
7.4 KiB
/* |
|
* Copyright (c) 2004 Lubos Lunak <l.lunak@kde.org> |
|
* |
|
* 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 |
|
* (at your option) 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. |
|
* |
|
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
*/ |
|
|
|
#include "detectwidget.h" |
|
#include "../../plugins/platforms/x11/standalone/x11cursor.h" |
|
|
|
#include <KLocalizedString> |
|
#include <QDebug> |
|
#include <kwindowsystem.h> |
|
#include <QLabel> |
|
#include <QRadioButton> |
|
#include <QCheckBox> |
|
#include <QDialogButtonBox> |
|
#include <QMouseEvent> |
|
#include <QEvent> |
|
#include <QByteArray> |
|
#include <QTimer> |
|
|
|
#include <X11/Xlib.h> |
|
#include <X11/Xatom.h> |
|
#include <X11/Xutil.h> |
|
#include <fixx11h.h> |
|
#include <QX11Info> |
|
|
|
namespace KWin |
|
{ |
|
|
|
DetectWidget::DetectWidget(QWidget* parent) |
|
: QWidget(parent) |
|
{ |
|
setupUi(this); |
|
} |
|
|
|
DetectDialog::DetectDialog(QWidget* parent, const char* name) |
|
: QDialog(parent), |
|
grabber() |
|
{ |
|
setObjectName(name); |
|
setModal(true); |
|
setLayout(new QVBoxLayout); |
|
|
|
widget = new DetectWidget(this); |
|
layout()->addWidget(widget); |
|
|
|
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this); |
|
layout()->addWidget(buttons); |
|
|
|
connect(buttons, SIGNAL(accepted()), SLOT(accept())); |
|
connect(buttons, SIGNAL(rejected()), SLOT(reject())); |
|
} |
|
|
|
void DetectDialog::detect(WId window, int secs) |
|
{ |
|
if (window == 0) |
|
QTimer::singleShot(secs*1000, this, SLOT(selectWindow())); |
|
else |
|
readWindow(window); |
|
} |
|
|
|
void DetectDialog::readWindow(WId w) |
|
{ |
|
if (w == 0) { |
|
emit detectionDone(false); |
|
return; |
|
} |
|
info.reset(new KWindowInfo(w, NET::WMAllProperties, NET::WM2AllProperties)); // read everything |
|
if (!info->valid()) { |
|
emit detectionDone(false); |
|
return; |
|
} |
|
wmclass_class = info->windowClassClass(); |
|
wmclass_name = info->windowClassName(); |
|
role = info->windowRole(); |
|
type = info->windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask |
|
| NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask |
|
| NET::UtilityMask | NET::SplashMask); |
|
title = info->name(); |
|
machine = info->clientMachine(); |
|
executeDialog(); |
|
} |
|
|
|
void DetectDialog::executeDialog() |
|
{ |
|
static const char* const types[] = { |
|
I18N_NOOP("Normal Window"), |
|
I18N_NOOP("Desktop"), |
|
I18N_NOOP("Dock (panel)"), |
|
I18N_NOOP("Toolbar"), |
|
I18N_NOOP("Torn-Off Menu"), |
|
I18N_NOOP("Dialog Window"), |
|
I18N_NOOP("Override Type"), |
|
I18N_NOOP("Standalone Menubar"), |
|
I18N_NOOP("Utility Window"), |
|
I18N_NOOP("Splash Screen") |
|
}; |
|
widget->class_label->setText(wmclass_class + QLatin1String(" (") + wmclass_name + ' ' + wmclass_class + ')'); |
|
widget->role_label->setText(role); |
|
widget->match_role->setEnabled(!role.isEmpty()); |
|
if (type == NET::Unknown) |
|
widget->type_label->setText(i18n("Unknown - will be treated as Normal Window")); |
|
else |
|
widget->type_label->setText(i18n(types[ type ])); |
|
widget->title_label->setText(title); |
|
widget->machine_label->setText(machine); |
|
widget->adjustSize(); |
|
adjustSize(); |
|
if (width() < 4*height()/3) |
|
resize(4*height()/3, height()); |
|
emit detectionDone(exec() == QDialog::Accepted); |
|
} |
|
|
|
QByteArray DetectDialog::selectedClass() const |
|
{ |
|
if (widget->match_whole_class->isChecked()) |
|
return wmclass_name + ' ' + wmclass_class; |
|
return wmclass_class; |
|
} |
|
|
|
bool DetectDialog::selectedWholeClass() const |
|
{ |
|
return widget->match_whole_class->isChecked(); |
|
} |
|
|
|
QByteArray DetectDialog::selectedRole() const |
|
{ |
|
if (widget->match_role->isChecked()) |
|
return role; |
|
return ""; |
|
} |
|
|
|
QString DetectDialog::selectedTitle() const |
|
{ |
|
return title; |
|
} |
|
|
|
Rules::StringMatch DetectDialog::titleMatch() const |
|
{ |
|
return widget->match_title->isChecked() ? Rules::ExactMatch : Rules::UnimportantMatch; |
|
} |
|
|
|
bool DetectDialog::selectedWholeApp() const |
|
{ |
|
return !widget->match_type->isChecked(); |
|
} |
|
|
|
NET::WindowType DetectDialog::selectedType() const |
|
{ |
|
return type; |
|
} |
|
|
|
QByteArray DetectDialog::selectedMachine() const |
|
{ |
|
return machine; |
|
} |
|
|
|
void DetectDialog::selectWindow() |
|
{ |
|
if (!KWin::Cursor::self()) { |
|
qApp->setProperty("x11Connection", QVariant::fromValue<void*>(QX11Info::connection())); |
|
qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); |
|
new X11Cursor(this); |
|
} |
|
// use a dialog, so that all user input is blocked |
|
// use WX11BypassWM and moving away so that it's not actually visible |
|
// grab only mouse, so that keyboard can be used e.g. for switching windows |
|
grabber.reset(new QDialog(nullptr, Qt::X11BypassWindowManagerHint)); |
|
grabber->move(-1000, -1000); |
|
grabber->setModal(true); |
|
grabber->show(); |
|
// Qt uses QX11Info::appTime() to grab the pointer, what can silently fail (#318437) ... |
|
XSync(QX11Info::display(), false); |
|
if (XGrabPointer(QX11Info::display(), grabber->winId(), false, ButtonReleaseMask, |
|
GrabModeAsync, GrabModeAsync, None, KWin::Cursor::x11Cursor(Qt::CrossCursor), |
|
CurrentTime) == Success) { // ...so we use the far more convincing CurrentTime |
|
QCoreApplication::instance()->installNativeEventFilter(this); |
|
} else { |
|
// ... and if we fail, cleanup, so we won't receive random events |
|
grabber.reset(); |
|
} |
|
} |
|
|
|
bool DetectDialog::nativeEventFilter(const QByteArray &eventType, void *message, long int*) |
|
{ |
|
if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { |
|
return false; |
|
} |
|
auto *event = reinterpret_cast<xcb_generic_event_t *>(message); |
|
if ((event->response_type & ~0x80) != XCB_BUTTON_RELEASE) { |
|
return false; |
|
} |
|
QCoreApplication::instance()->removeNativeEventFilter(this); |
|
grabber.reset(); |
|
auto *me = reinterpret_cast<xcb_button_press_event_t*>(event); |
|
if (me->detail != XCB_BUTTON_INDEX_1) { |
|
emit detectionDone(false); |
|
return true; |
|
} |
|
readWindow(findWindow()); |
|
return true; |
|
} |
|
|
|
WId DetectDialog::findWindow() |
|
{ |
|
Window root; |
|
Window child; |
|
uint mask; |
|
int rootX, rootY, x, y; |
|
Window parent = QX11Info::appRootWindow(); |
|
Atom wm_state = XInternAtom(QX11Info::display(), "WM_STATE", False); |
|
for (int i = 0; |
|
i < 10; |
|
++i) { |
|
XQueryPointer(QX11Info::display(), parent, &root, &child, |
|
&rootX, &rootY, &x, &y, &mask); |
|
if (child == None) |
|
return 0; |
|
Atom type; |
|
int format; |
|
unsigned long nitems, after; |
|
unsigned char* prop; |
|
if (XGetWindowProperty(QX11Info::display(), child, wm_state, 0, 0, False, AnyPropertyType, |
|
&type, &format, &nitems, &after, &prop) == Success) { |
|
if (prop != nullptr) |
|
XFree(prop); |
|
if (type != None) |
|
return child; |
|
} |
|
parent = child; |
|
} |
|
return 0; |
|
} |
|
|
|
} // namespace |
|
|
|
|