diff --git a/CMakeLists.txt b/CMakeLists.txt index fbdf139ded..1e43bfc54c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,7 @@ set(kwin_KDEINIT_SRCS workspace.cpp dbusinterface.cpp client.cpp + client_machine.cpp tabgroup.cpp placement.cpp atoms.cpp diff --git a/client.cpp b/client.cpp index 3d334a1bbe..ad5c3db04c 100644 --- a/client.cpp +++ b/client.cpp @@ -41,6 +41,7 @@ along with this program. If not, see . #include #include "bridge.h" +#include "client_machine.h" #include "composite.h" #include "group.h" #include "workspace.h" @@ -211,6 +212,8 @@ Client::Client(Workspace* ws) connect(this, SIGNAL(clientStartUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged())); connect(this, SIGNAL(clientFinishUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged())); + connect(clientMachine(), SIGNAL(localhostChanged()), SLOT(updateCaption())); + // SELI TODO: Initialize xsizehints?? } @@ -1373,21 +1376,20 @@ void Client::killProcess(bool ask, Time timestamp) if (m_killHelperPID && !::kill(m_killHelperPID, 0)) // means the process is alive return; Q_ASSERT(!ask || timestamp != CurrentTime); - QByteArray machine = wmClientMachine(true); pid_t pid = info->pid(); - if (pid <= 0 || machine.isEmpty()) // Needed properties missing + if (pid <= 0 || clientMachine()->hostName().isEmpty()) // Needed properties missing return; - kDebug(1212) << "Kill process:" << pid << "(" << machine << ")"; + kDebug(1212) << "Kill process:" << pid << "(" << clientMachine()->hostName() << ")"; if (!ask) { - if (machine != "localhost") { + if (!clientMachine()->isLocal()) { QStringList lst; - lst << machine << "kill" << QString::number(pid); + lst << clientMachine()->hostName() << "kill" << QString::number(pid); QProcess::startDetached("xon", lst); } else ::kill(pid, SIGTERM); } else { QProcess::startDetached(KStandardDirs::findExe("kwin_killer_helper"), - QStringList() << "--pid" << QByteArray().setNum(unsigned(pid)) << "--hostname" << machine + QStringList() << "--pid" << QByteArray().setNum(unsigned(pid)) << "--hostname" << clientMachine()->hostName() << "--windowname" << caption() << "--applicationname" << resourceClass() << "--wid" << QString::number(window()) @@ -1774,8 +1776,8 @@ void Client::setCaption(const QString& _s, bool force) cap_suffix.clear(); QString machine_suffix; if (!options->condensedTitle()) { // machine doesn't qualify for "clean" - if (wmClientMachine(false) != "localhost" && !isLocalMachine(wmClientMachine(false))) - machine_suffix = QString(" <@") + wmClientMachine(true) + '>' + LRM; + if (clientMachine()->hostName() != ClientMachine::localhost() && !clientMachine()->isLocal()) + machine_suffix = QString(" <@") + clientMachine()->hostName() + '>' + LRM; } QString shortcut_suffix = !shortcut().isEmpty() ? (" {" + shortcut().toString() + '}') : QString(); cap_suffix = machine_suffix + shortcut_suffix; diff --git a/client.h b/client.h index dbc7ca9d86..4cbc20f8e2 100644 --- a/client.h +++ b/client.h @@ -499,7 +499,6 @@ public: void updateCompositeBlocking(bool readProperty = false); QString caption(bool full = true, bool stripped = false) const; - void updateCaption(); void keyPressEvent(uint key_code); // FRAME ?? void updateMouseGrab(); @@ -654,6 +653,7 @@ public: public slots: void closeWindow(); + void updateCaption(); private slots: void autoRaise(); diff --git a/client_machine.cpp b/client_machine.cpp new file mode 100644 index 0000000000..c7821b4629 --- /dev/null +++ b/client_machine.cpp @@ -0,0 +1,237 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2013 Martin Gräßlin + +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, see . +*********************************************************************/ +// own +#include "client_machine.h" +// KWin +#include "utils.h" +// KDE +#include +// Qt +#include +#include +// system +#include +#include + +namespace KWin { + +static QByteArray getHostName() +{ +#ifdef HOST_NAME_MAX + char hostnamebuf[HOST_NAME_MAX]; +#else + char hostnamebuf[256]; +#endif + if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { + hostnamebuf[sizeof(hostnamebuf)-1] = 0; + return QByteArray(hostnamebuf); + } + return QByteArray(); +} + +GetAddrInfo::GetAddrInfo(const QByteArray &hostName, QObject *parent) + : QObject(parent) + , m_resolving(false) + , m_resolved(false) + , m_ownResolved(false) + , m_hostName(hostName) + , m_addressHints(new addrinfo) + , m_address(NULL) + , m_ownAddress(NULL) + , m_watcher(new QFutureWatcher(this)) + , m_ownAddressWatcher(new QFutureWatcher(this)) +{ + // watcher will be deleted together with the GetAddrInfo once the future + // got canceled or finished + connect(m_watcher, SIGNAL(canceled()), SLOT(deleteLater())); + connect(m_watcher, SIGNAL(finished()), SLOT(slotResolved())); + connect(m_ownAddressWatcher, SIGNAL(canceled()), SLOT(deleteLater())); + connect(m_ownAddressWatcher, SIGNAL(finished()), SLOT(slotOwnAddressResolved())); +} + +GetAddrInfo::~GetAddrInfo() +{ + if (m_watcher && m_watcher->isRunning()) { + m_watcher->cancel(); + } + if (m_ownAddressWatcher && m_ownAddressWatcher->isRunning()) { + m_ownAddressWatcher->cancel(); + } + if (m_address) { + freeaddrinfo(m_address); + } + if (m_ownAddress) { + freeaddrinfo(m_ownAddress); + } + delete m_addressHints; +} + +void GetAddrInfo::resolve() +{ + if (m_resolving) { + return; + } + m_resolving = true; + memset(m_addressHints, 0, sizeof(*m_addressHints)); + m_addressHints->ai_family = PF_UNSPEC; + m_addressHints->ai_socktype = SOCK_STREAM; + m_addressHints->ai_flags |= AI_CANONNAME; + + // TODO: C++11 nullptr + const char* nullPtr = NULL; + m_watcher->setFuture(QtConcurrent::run(getaddrinfo, m_hostName, nullPtr, m_addressHints, &m_address)); + m_ownAddressWatcher->setFuture(QtConcurrent::run(getaddrinfo, getHostName(), nullPtr, m_addressHints, &m_ownAddress)); +} + +void GetAddrInfo::slotResolved() +{ + if (resolved(m_watcher)) { + m_resolved = true; + compare(); + } +} + +void GetAddrInfo::slotOwnAddressResolved() +{ + if (resolved(m_ownAddressWatcher)) { + m_ownResolved = true; + compare(); + } +} + +bool GetAddrInfo::resolved(QFutureWatcher< int >* watcher) +{ + if (!watcher->isFinished()) { + return false; + } + if (watcher->result() != 0) { + kDebug(1212) << "getaddrinfo failed with error:" << gai_strerror(watcher->result()); + // call failed; + deleteLater(); + return false; + } + return true; +} + +void GetAddrInfo::compare() +{ + if (!m_resolved || !m_ownResolved) { + return; + } + addrinfo *address = m_address; + while (address) { + if (address->ai_canonname && m_hostName == QByteArray(address->ai_canonname).toLower()) { + addrinfo *ownAddress = m_ownAddress; + bool localFound = false; + while (ownAddress) { + if (ownAddress->ai_canonname && QByteArray(ownAddress->ai_canonname).toLower() == m_hostName) { + localFound = true; + break; + } + ownAddress = ownAddress->ai_next; + } + if (localFound) { + emit local(); + break; + } + } + address = address->ai_next; + } + deleteLater(); +} + + +ClientMachine::ClientMachine(QObject *parent) + : QObject(parent) + , m_localhost(false) + , m_resolved(false) + , m_resolving(false) +{ +} + +ClientMachine::~ClientMachine() +{ +} + +void ClientMachine::resolve(xcb_window_t window, xcb_window_t clientLeader) +{ + if (m_resolved) { + return; + } + QByteArray name = getStringProperty(window, XCB_ATOM_WM_CLIENT_MACHINE); + if (name.isEmpty() && clientLeader && clientLeader != window) { + name = getStringProperty(clientLeader, XCB_ATOM_WM_CLIENT_MACHINE); + } + if (name.isEmpty()) { + name = localhost(); + } + if (name == localhost()) { + setLocal(); + } + m_hostName = name; + checkForLocalhost(); + m_resolved = true; +} + +void ClientMachine::checkForLocalhost() +{ + if (isLocal()) { + // nothing to do + return; + } + QByteArray host = getHostName(); + + if (!host.isEmpty()) { + host = host.toLower(); + const QByteArray lowerHostName(m_hostName.toLower()); + if (host == lowerHostName) { + setLocal(); + return; + } + if (char *dot = strchr(host.data(), '.')) { + *dot = '\0'; + if (host == lowerHostName) { + setLocal(); + return; + } + } else { + m_resolving = true; + // check using information from get addr info + // GetAddrInfo gets automatically destroyed once it finished or not + GetAddrInfo *info = new GetAddrInfo(lowerHostName, this); + connect(info, SIGNAL(local()), SLOT(setLocal())); + connect(info, SIGNAL(destroyed(QObject*)), SLOT(resolveFinished())); + info->resolve(); + } + } +} + +void ClientMachine::setLocal() +{ + m_localhost = true; + emit localhostChanged(); +} + +void ClientMachine::resolveFinished() +{ + m_resolving = false; +} + +} // namespace diff --git a/client_machine.h b/client_machine.h new file mode 100644 index 0000000000..e56ffc70cf --- /dev/null +++ b/client_machine.h @@ -0,0 +1,117 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2013 Martin Gräßlin + +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, see . +*********************************************************************/ +#ifndef KWIN_CLIENT_MACHINE_H +#define KWIN_CLIENT_MACHINE_H + +#include +#include + +// forward declaration +struct addrinfo; +template +class QFutureWatcher; + +namespace KWin { + +class GetAddrInfo : public QObject +{ + Q_OBJECT +public: + explicit GetAddrInfo(const QByteArray &hostName, QObject *parent = NULL); + virtual ~GetAddrInfo(); + + void resolve(); + +Q_SIGNALS: + void local(); + +private Q_SLOTS: + void slotResolved(); + void slotOwnAddressResolved(); + +private: + void compare(); + bool resolved(QFutureWatcher *watcher); + bool m_resolving; + bool m_resolved; + bool m_ownResolved; + QByteArray m_hostName; + addrinfo *m_addressHints; + addrinfo *m_address; + addrinfo *m_ownAddress; + QFutureWatcher *m_watcher; + QFutureWatcher *m_ownAddressWatcher; +}; + +class ClientMachine : public QObject +{ + Q_OBJECT +public: + explicit ClientMachine(QObject *parent = NULL); + virtual ~ClientMachine(); + + void resolve(xcb_window_t window, xcb_window_t clientLeader); + const QByteArray &hostName() const; + bool isLocal() const; + static QByteArray localhost(); + bool isResolving() const; + +Q_SIGNALS: + void localhostChanged(); + +private Q_SLOTS: + void setLocal(); + void resolveFinished(); + +private: + void checkForLocalhost(); + QByteArray m_hostName; + bool m_localhost; + bool m_resolved; + bool m_resolving; +}; + +inline +bool ClientMachine::isLocal() const +{ + return m_localhost; +} + +inline +const QByteArray &ClientMachine::hostName() const +{ + return m_hostName; +} + +inline +QByteArray ClientMachine::localhost() +{ + return "localhost"; +} + +inline +bool ClientMachine::isResolving() const +{ + return m_resolving; +} + +} // namespace + +#endif diff --git a/kcmkwin/kwinrules/CMakeLists.txt b/kcmkwin/kwinrules/CMakeLists.txt index 56dbf4b068..1d06cd3a31 100644 --- a/kcmkwin/kwinrules/CMakeLists.txt +++ b/kcmkwin/kwinrules/CMakeLists.txt @@ -1,7 +1,7 @@ ADD_DEFINITIONS(-DKCMRULES) ########### next target ############### -set (kwinrules_MOC_HDRS yesnobox.h) +set (kwinrules_MOC_HDRS yesnobox.h ../../client_machine.h) qt4_wrap_cpp(kwinrules_MOC_SRCS ${kwinrules_MOC_HDRS}) set(kwinrules_SRCS ruleswidget.cpp ruleslist.cpp kwinsrc.cpp detectwidget.cpp ${kwinrules_MOC_SRCS}) diff --git a/kcmkwin/kwinrules/kwinsrc.cpp b/kcmkwin/kwinrules/kwinsrc.cpp index 1ea305dc0d..bc14986f12 100644 --- a/kcmkwin/kwinrules/kwinsrc.cpp +++ b/kcmkwin/kwinrules/kwinsrc.cpp @@ -24,3 +24,4 @@ #include "../../placement.cpp" #include "../../options.cpp" #include "../../utils.cpp" +#include "../../client_machine.cpp" diff --git a/kcmkwin/kwinrules/main.cpp b/kcmkwin/kwinrules/main.cpp index 194349c86d..b040172e7f 100644 --- a/kcmkwin/kwinrules/main.cpp +++ b/kcmkwin/kwinrules/main.cpp @@ -27,6 +27,7 @@ #include "ruleswidget.h" #include "../../rules.h" +#include "../../client_machine.h" #include namespace KWin @@ -72,6 +73,8 @@ static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app) NET::WM2WindowClass | NET::WM2WindowRole | NET::WM2ClientMachine); if (!info.valid()) // shouldn't really happen return NULL; + ClientMachine clientMachine; + clientMachine.resolve(info.win(), info.groupLeader()); QByteArray wmclass_class = info.windowClassClass().toLower(); QByteArray wmclass_name = info.windowClassName().toLower(); QByteArray role = info.windowRole().toLower(); @@ -79,7 +82,7 @@ static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app) | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); QString title = info.name(); - QByteArray machine = info.clientMachine().toLower(); + QByteArray machine = clientMachine.hostName(); Rules* best_match = NULL; int match_quality = 0; for (QList< Rules* >::ConstIterator it = rules.constBegin(); @@ -126,7 +129,7 @@ static Rules* findRule(const QList< Rules* >& rules, Window wid, bool whole_app) if (!rule->matchType(type) || !rule->matchRole(role) || !rule->matchTitle(title) - || !rule->matchClientMachine(machine)) + || !rule->matchClientMachine(machine, clientMachine.isLocal())) continue; if (quality > match_quality) { best_match = rule; diff --git a/rules.cpp b/rules.cpp index f29e320e7e..8a1edc8531 100644 --- a/rules.cpp +++ b/rules.cpp @@ -30,6 +30,7 @@ along with this program. If not, see . #ifndef KCMRULES #include #include "client.h" +#include "client_machine.h" #include "workspace.h" #endif @@ -394,12 +395,12 @@ bool Rules::matchTitle(const QString& match_title) const return true; } -bool Rules::matchClientMachine(const QByteArray& match_machine) const +bool Rules::matchClientMachine(const QByteArray& match_machine, bool local) const { if (clientmachinematch != UnimportantMatch) { // if it's localhost, check also "localhost" before checking hostname - if (match_machine != "localhost" && isLocalMachine(match_machine) - && matchClientMachine("localhost")) + if (match_machine != "localhost" && local + && matchClientMachine("localhost", true)) return true; if (clientmachinematch == RegExpMatch && QRegExp(clientmachine).indexIn(match_machine) == -1) @@ -425,7 +426,7 @@ bool Rules::match(const Client* c) const return false; if (!matchTitle(c->caption(false))) return false; - if (!matchClientMachine(c->wmClientMachine(false))) + if (!matchClientMachine(c->clientMachine()->hostName(), c->clientMachine()->isLocal())) return false; return true; } diff --git a/rules.h b/rules.h index 8be58f79e9..2299dd8593 100644 --- a/rules.h +++ b/rules.h @@ -160,7 +160,7 @@ private: bool matchWMClass(const QByteArray& match_class, const QByteArray& match_name) const; bool matchRole(const QByteArray& match_role) const; bool matchTitle(const QString& match_title) const; - bool matchClientMachine(const QByteArray& match_machine) const; + bool matchClientMachine(const QByteArray& match_machine, bool local) const; // All these values are saved to the cfg file, and are also used in kstart! enum { Unused = 0, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 918a480f66..739483d112 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,3 +33,20 @@ target_link_libraries( testVirtualDesktops ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY} ) + +######################################################## +# Test ClientMachine +######################################################## +set( testClientMachine_SRCS + test_client_machine.cpp + ../client_machine.cpp +) +kde4_add_unit_test( testClientMachine TESTNAME kwin-TestClientMachine ${testClientMachine_SRCS} ) + +target_link_libraries( testClientMachine + ${QT_QTTEST_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${KDE4_KDEUI_LIBS} + ${XCB_XCB_LIBRARIES} + ${X11_XCB_LIBRARIES} +) diff --git a/tests/test_client_machine.cpp b/tests/test_client_machine.cpp new file mode 100644 index 0000000000..bccb691c3b --- /dev/null +++ b/tests/test_client_machine.cpp @@ -0,0 +1,197 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2013 Martin Gräßlin + +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, see . +*********************************************************************/ +// KWin +#include "../client_machine.h" +#include "../utils.h" +// Qt +#include +#include +// xcb +#include +// system +#include +#include + +namespace KWin { + +// mock required function from utils +QByteArray getStringProperty(WId w, Atom prop, char separator) +{ + Q_UNUSED(separator) + ScopedCPointer property(xcb_get_property_reply(connection(), + xcb_get_property_unchecked(connection(), false, w, prop, XCB_ATOM_STRING, 0, 10000), + NULL)); + if (property.isNull()) { + return QByteArray(); + } + void *data = xcb_get_property_value(property.data()); + if (data && property->value_len > 0) { + QByteArray result = QByteArray((const char*) data, property->value_len); + return result; + } + return QByteArray(); +} + +} + +using namespace KWin; + +class TestClientMachine : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + void hostName_data(); + void hostName(); + void emptyHostName(); + +private: + xcb_window_t createWindow(); + void setClientMachineProperty(xcb_window_t window, const QByteArray &hostname); + xcb_window_t m_testWindow; + QByteArray m_hostName; + QByteArray m_fqdn; +}; + +xcb_window_t TestClientMachine::createWindow() +{ + xcb_window_t w = xcb_generate_id(connection()); + const uint32_t values[] = { true }; + xcb_create_window(connection(), 0, w, rootWindow(), + 0, 0, 10, 10, + 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT, values); + return w; +} + +void TestClientMachine::setClientMachineProperty(xcb_window_t window, const QByteArray &hostname) +{ + xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, window, + XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, + hostname.length(), hostname.constData()); +} + +void TestClientMachine::initTestCase() +{ +#ifdef HOST_NAME_MAX + char hostnamebuf[HOST_NAME_MAX]; +#else + char hostnamebuf[256]; +#endif + if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { + hostnamebuf[sizeof(hostnamebuf)-1] = 0; + m_hostName = hostnamebuf; + } + addrinfo *res; + addrinfo addressHints; + memset(&addressHints, 0, sizeof(addressHints)); + addressHints.ai_family = PF_UNSPEC; + addressHints.ai_socktype = SOCK_STREAM; + addressHints.ai_flags |= AI_CANONNAME; + if (getaddrinfo(m_hostName.constData(), NULL, &addressHints, &res) == 0) { + if (res->ai_canonname) { + m_fqdn = QByteArray(res->ai_canonname); + } + } + freeaddrinfo(res); +} + +void TestClientMachine::cleanupTestCase() +{ +} + +void TestClientMachine::init() +{ + m_testWindow = XCB_WINDOW_NONE; +} + +void TestClientMachine::cleanup() +{ + if (m_testWindow != XCB_WINDOW_NONE) { + xcb_destroy_window(connection(), m_testWindow); + } +} + +void TestClientMachine::hostName_data() +{ + QTest::addColumn("hostName"); + QTest::addColumn("expectedHost"); + QTest::addColumn("local"); + + QTest::newRow("empty") << QByteArray() << QByteArray("localhost") << true; + QTest::newRow("localhost") << QByteArray("localhost") << QByteArray("localhost") << true; + QTest::newRow("hostname") << m_hostName << m_hostName << true; + QTest::newRow("HOSTNAME") << m_hostName.toUpper() << m_hostName.toUpper() << true; + QByteArray cutted(m_hostName); + cutted.remove(0, 1); + QTest::newRow("ostname") << cutted << cutted << false; + QByteArray domain("random.name.not.exist.tld"); + QTest::newRow("domain") << domain << domain << false; + QTest::newRow("fqdn") << m_fqdn << m_fqdn << true; + QTest::newRow("FQDN") << m_fqdn.toUpper() << m_fqdn.toUpper() << true; + cutted = m_fqdn; + cutted.remove(0, 1); + QTest::newRow("qdn") << cutted << cutted << false; +} + +void TestClientMachine::hostName() +{ + xcb_window_t window = createWindow(); + QFETCH(QByteArray, hostName); + QFETCH(bool, local); + setClientMachineProperty(window, hostName); + + ClientMachine clientMachine; + QSignalSpy spy(&clientMachine, SIGNAL(localhostChanged())); + clientMachine.resolve(window, XCB_WINDOW_NONE); + QTEST(clientMachine.hostName(), "expectedHost"); + + int i=0; + while (clientMachine.isResolving() && i++ < 50) { + // name is being resolved in an external thread, so let's wait a little bit + QTest::qWait(250); + } + + QCOMPARE(clientMachine.isLocal(), local); + QCOMPARE(spy.isEmpty(), !local); +} + +void TestClientMachine::emptyHostName() +{ + xcb_window_t window = createWindow(); + ClientMachine clientMachine; + QSignalSpy spy(&clientMachine, SIGNAL(localhostChanged())); + clientMachine.resolve(window, XCB_WINDOW_NONE); + QCOMPARE(clientMachine.hostName(), ClientMachine::localhost()); + QVERIFY(clientMachine.isLocal()); + // should be local + QCOMPARE(spy.isEmpty(), false); +} + +// need to implement main manually as we need a QApplication for X11 interaction +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + TestClientMachine tc; + return QTest::qExec(&tc, argc, argv); +} +#include "test_client_machine.moc" diff --git a/toplevel.cpp b/toplevel.cpp index 6c718100c0..3b426f77b6 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -24,6 +24,7 @@ along with this program. If not, see . #include "atoms.h" #include "client.h" +#include "client_machine.h" #include "effects.h" #include "shadow.h" @@ -42,6 +43,7 @@ Toplevel::Toplevel(Workspace* ws) , damage_handle(None) , is_shape(false) , effect_window(NULL) + , m_clientMachine(new ClientMachine(this)) , wmClientLeaderWin(0) , unredirect(false) , unredirectSuspend(false) @@ -132,7 +134,8 @@ void Toplevel::copyToDeleted(Toplevel* c) effect_window->setWindow(this); resource_name = c->resourceName(); resource_class = c->resourceClass(); - client_machine = c->wmClientMachine(false); + m_clientMachine = c->m_clientMachine; + m_clientMachine->setParent(this); wmClientLeaderWin = c->wmClientLeader(); window_role = c->windowRole(); opaque_region = c->opaqueRegion(); @@ -233,11 +236,7 @@ QByteArray Toplevel::wmCommand() void Toplevel::getWmClientMachine() { - client_machine = getStringProperty(window(), XA_WM_CLIENT_MACHINE); - if (client_machine.isEmpty() && wmClientLeaderWin && wmClientLeaderWin != window()) - client_machine = getStringProperty(wmClientLeaderWin, XA_WM_CLIENT_MACHINE); - if (client_machine.isEmpty()) - client_machine = "localhost"; + m_clientMachine->resolve(window(), wmClientLeader()); } /*! @@ -246,13 +245,15 @@ void Toplevel::getWmClientMachine() */ QByteArray Toplevel::wmClientMachine(bool use_localhost) const { - QByteArray result = client_machine; - if (use_localhost) { - // special name for the local machine (localhost) - if (result != "localhost" && isLocalMachine(result)) - result = "localhost"; + if (!m_clientMachine) { + // this should never happen + return QByteArray(); } - return result; + if (use_localhost && m_clientMachine->isLocal()) { + // special name for the local machine (localhost) + return ClientMachine::localhost(); + } + return m_clientMachine->hostName(); } /*! diff --git a/toplevel.h b/toplevel.h index 05242d6f3c..1c8f1ac20e 100644 --- a/toplevel.h +++ b/toplevel.h @@ -41,6 +41,7 @@ class NETWinInfo2; namespace KWin { +class ClientMachine; class Workspace; class EffectWindowImpl; class Shadow; @@ -223,6 +224,7 @@ public: QByteArray resourceClass() const; QByteArray wmCommand(); QByteArray wmClientMachine(bool use_localhost) const; + const ClientMachine *clientMachine() const; Window wmClientLeader() const; pid_t pid() const; static bool resourceMatch(const Toplevel* c1, const Toplevel* c2); @@ -383,7 +385,7 @@ private: EffectWindowImpl* effect_window; QByteArray resource_name; QByteArray resource_class; - QByteArray client_machine; + ClientMachine *m_clientMachine; WId wmClientLeaderWin; QByteArray window_role; bool unredirect; @@ -646,6 +648,11 @@ inline bool Toplevel::unredirected() const return unredirect; } +inline const ClientMachine *Toplevel::clientMachine() const +{ + return m_clientMachine; +} + QDebug& operator<<(QDebug& stream, const Toplevel*); QDebug& operator<<(QDebug& stream, const ToplevelList&); QDebug& operator<<(QDebug& stream, const ConstToplevelList&); diff --git a/utils.cpp b/utils.cpp index 070bc977eb..1079043de1 100644 --- a/utils.cpp +++ b/utils.cpp @@ -27,12 +27,12 @@ along with this program. If not, see . #include "utils.h" -#include +#include +#include #ifndef KCMRULES #include #include -#include #include #include #include @@ -43,7 +43,6 @@ along with this program. If not, see . #include #include -#include #include #include @@ -179,6 +178,8 @@ bool KWinSelectionOwner::genericReply(Atom target_P, Atom property_P, Window req Atom KWinSelectionOwner::xa_version = None; +#endif + QByteArray getStringProperty(WId w, Atom prop, char separator) { Atom type; @@ -204,6 +205,7 @@ QByteArray getStringProperty(WId w, Atom prop, char separator) return result; } +#ifndef KCMRULES static Time next_x_time; static Bool update_x_time_predicate(Display*, XEvent* event, XPointer) { @@ -406,33 +408,6 @@ Qt::KeyboardModifiers x11ToQtKeyboardModifiers(int state) #endif -bool isLocalMachine(const QByteArray& host) -{ -#ifdef HOST_NAME_MAX - char hostnamebuf[HOST_NAME_MAX]; -#else - char hostnamebuf[256]; -#endif - if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { - hostnamebuf[sizeof(hostnamebuf)-1] = 0; - if (host == hostnamebuf) - return true; - if (char *dot = strchr(hostnamebuf, '.')) { - *dot = '\0'; - if (host == hostnamebuf) - return true; - } else { // e.g. LibreOffice likes to give FQDN, even if gethostname() doesn't include domain - QByteArray h = hostnamebuf; - if( getdomainname( hostnamebuf, sizeof hostnamebuf ) >= 0 ) { - hostnamebuf[sizeof(hostnamebuf)-1] = 0; - if( host == h + '.' + QByteArray( hostnamebuf )) - return true; - } - } - } - return false; -} - #ifndef KCMRULES ShortcutDialog::ShortcutDialog(const QKeySequence& cut) : _shortcut(cut) diff --git a/utils.h b/utils.h index e0e565248c..e6b9a679be 100644 --- a/utils.h +++ b/utils.h @@ -363,8 +363,6 @@ Time timestampDiff(Time time1, Time time2) // returns time2 - time1 return NET::timestampDiff(time1, time2); } -bool isLocalMachine(const QByteArray& host); - QPoint cursorPos(); // converting between X11 mouse/keyboard state mask and Qt button/keyboard states