diff --git a/client_machine.cpp b/client_machine.cpp index c7821b462..6c12a2b8c 100644 --- a/client_machine.cpp +++ b/client_machine.cpp @@ -1,237 +1,239 @@ /******************************************************************** 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 +#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/tests/test_client_machine.cpp b/tests/test_client_machine.cpp index 1129c91b3..4f7125e21 100644 --- a/tests/test_client_machine.cpp +++ b/tests/test_client_machine.cpp @@ -1,181 +1,183 @@ /******************************************************************** 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 . *********************************************************************/ #include "testutils.h" // KWin #include "../client_machine.h" #include "../utils.h" // Qt #include #include // xcb #include // system #include +#include +#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: void setClientMachineProperty(xcb_window_t window, const QByteArray &hostname); xcb_window_t m_testWindow; QByteArray m_hostName; QByteArray m_fqdn; }; 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() { m_testWindow = createWindow(); QFETCH(QByteArray, hostName); QFETCH(bool, local); setClientMachineProperty(m_testWindow, hostName); ClientMachine clientMachine; QSignalSpy spy(&clientMachine, SIGNAL(localhostChanged())); clientMachine.resolve(m_testWindow, 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() { m_testWindow = createWindow(); ClientMachine clientMachine; QSignalSpy spy(&clientMachine, SIGNAL(localhostChanged())); clientMachine.resolve(m_testWindow, XCB_WINDOW_NONE); QCOMPARE(clientMachine.hostName(), ClientMachine::localhost()); QVERIFY(clientMachine.isLocal()); // should be local QCOMPARE(spy.isEmpty(), false); } KWIN_TEST_MAIN(TestClientMachine) #include "test_client_machine.moc"