1
0
mirror of https://gitee.com/drabel/LibQQt.git synced 2025-01-04 10:18:44 +08:00
LibQQt/qdevicewatcher_linux.cpp
tianduanrui 3f857669fc init
2016-12-29 16:49:18 +08:00

289 lines
8.1 KiB
C++
Executable File

/******************************************************************************
QDeviceWatcherPrivate: watching depends on platform
Copyright (C) 2011-2015 Wang Bin <wbsecg1@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
******************************************************************************/
#include "qdevicewatcher.h"
#include "qdevicewatcher_p.h"
#ifdef Q_OS_LINUX
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)
#else
#endif
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#include <unistd.h>
#include <QtCore/QCoreApplication>
#include <QtCore/qregexp.h>
#include <QtCore/QTime>
#if CONFIG_SOCKETNOTIFIER
#include <QtCore/QSocketNotifier>
#elif CONFIG_TCPSOCKET
#include <QtNetwork/QTcpSocket>
#endif
#define UEVENT_BUFFER_SIZE 2048
enum udev_monitor_netlink_group {
UDEV_MONITOR_NONE,
UDEV_MONITOR_KERNEL,
UDEV_MONITOR_UDEV
};
QDeviceWatcherPrivate::~QDeviceWatcherPrivate()
{
stop();
close(netlink_socket);
netlink_socket = -1;
}
bool QDeviceWatcherPrivate::start()
{
if (!init())
return false;
#if CONFIG_SOCKETNOTIFIER
socket_notifier->setEnabled(true);
#elif CONFIG_TCPSOCKET
connect(tcp_socket, SIGNAL(readyRead()), SLOT(parseDeviceInfo()));
#else
this->QThread::start();
#endif
return true;
}
bool QDeviceWatcherPrivate::stop()
{
if (netlink_socket!=-1) {
#if CONFIG_SOCKETNOTIFIER
socket_notifier->setEnabled(false);
#elif CONFIG_TCPSOCKET
//tcp_socket->close(); //how to restart?
disconnect(this, SLOT(parseDeviceInfo()));
#else
this->quit();
#endif
close(netlink_socket);
netlink_socket = -1;
}
return true;
}
void QDeviceWatcherPrivate::parseDeviceInfo()
{
//zDebug("%s active", qPrintable(QTime::currentTime().toString()));
QByteArray data;
#if CONFIG_SOCKETNOTIFIER
//socket_notifier->setEnabled(false); //for win
data.resize(UEVENT_BUFFER_SIZE*2);
data.fill(0);
size_t len = read(socket_notifier->socket(), data.data(), UEVENT_BUFFER_SIZE*2);
//zDebug("read fro socket %d bytes", len);
data.resize(len);
//socket_notifier->setEnabled(true); //for win
#elif CONFIG_TCPSOCKET
data = tcp_socket->readAll();
#endif
data = data.replace(0, '\n').trimmed(); //In the original line each information is seperated by 0
if (buffer.isOpen())
buffer.close();
buffer.setBuffer(&data);
buffer.open(QIODevice::ReadOnly);
while(!buffer.atEnd()) { //buffer.canReadLine() always false?
parseLine(buffer.readLine().trimmed());
}
buffer.close();
}
#if CONFIG_THREAD
//another thread
void QDeviceWatcherPrivate::run()
{
QByteArray data;
//loop only when event happens. because of recv() block the function?
while (1) {
//char buf[UEVENT_BUFFER_SIZE*2] = {0};
//recv(d->netlink_socket, &buf, sizeof(buf), 0);
data.resize(UEVENT_BUFFER_SIZE*2);
data.fill(0);
size_t len = recv(netlink_socket, data.data(), data.size(), 0);
//zDebug("read fro socket %d bytes", len);
data.resize(len);
data = data.replace(0, '\n').trimmed();
if (buffer.isOpen())
buffer.close();
buffer.setBuffer(&data);
buffer.open(QIODevice::ReadOnly);
QByteArray line = buffer.readLine();
while(!line.isNull()) {
parseLine(line.trimmed());
line = buffer.readLine();
}
buffer.close();
}
}
#endif //CONFIG_THREAD
/**
* Create new udev monitor and connect to a specified event
* source. Valid sources identifiers are "udev" and "kernel".
*
* Applications should usually not connect directly to the
* "kernel" events, because the devices might not be useable
* at that time, before udev has configured them, and created
* device nodes.
*
* Accessing devices at the same time as udev, might result
* in unpredictable behavior.
*
* The "udev" events are sent out after udev has finished its
* event processing, all rules have been processed, and needed
* device nodes are created.
**/
bool QDeviceWatcherPrivate::init()
{
struct sockaddr_nl snl;
const int buffersize = 16 * 1024 * 1024;
int retval;
memset(&snl, 0x00, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
snl.nl_groups = UDEV_MONITOR_KERNEL;
netlink_socket = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
//netlink_socket = socket(PF_NETLINK, SOCK_DGRAM|SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT); //SOCK_CLOEXEC may be not available
if (netlink_socket == -1) {
qWarning("error getting socket: %s", strerror(errno));
return false;
}
/* set receive buffersize */
setsockopt(netlink_socket, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
retval = bind(netlink_socket, (struct sockaddr*) &snl, sizeof(struct sockaddr_nl));
if (retval < 0) {
qWarning("bind failed: %s", strerror(errno));
close(netlink_socket);
netlink_socket = -1;
return false;
} else if (retval == 0) {
//from libudev-monitor.c
struct sockaddr_nl _snl;
socklen_t _addrlen;
/*
* get the address the kernel has assigned us
* it is usually, but not necessarily the pid
*/
_addrlen = sizeof(struct sockaddr_nl);
retval = getsockname(netlink_socket, (struct sockaddr *)&_snl, &_addrlen);
if (retval == 0)
snl.nl_pid = _snl.nl_pid;
}
#if CONFIG_SOCKETNOTIFIER
socket_notifier = new QSocketNotifier(netlink_socket, QSocketNotifier::Read, this);
connect(socket_notifier, SIGNAL(activated(int)), SLOT(parseDeviceInfo())); //will always active
socket_notifier->setEnabled(false);
#elif CONFIG_TCPSOCKET
//QAbstractSocket *socket = new QAbstractSocket(QAbstractSocket::UnknownSocketType, this); //will not detect "remove", why?
tcp_socket = new QTcpSocket(this); //works too
if (!tcp_socket->setSocketDescriptor(netlink_socket, QAbstractSocket::ConnectedState)) {
qWarning("Failed to assign native socket to QAbstractSocket: %s", qPrintable(tcp_socket->errorString()));
delete tcp_socket;
return false;
}
#endif
return true;
}
void QDeviceWatcherPrivate::parseLine(const QByteArray &line)
{
//zDebug("%s", line.constData());
QString action_str;
QString dev;
#ifdef __MIPS_LINUX__
if (line.contains("/event")) //event
{
action_str = line.left(line.indexOf('@')).toLower();
dev = "/dev/input/" + line.right(line.length() - line.lastIndexOf('/') - 1);
}
else
{
#endif
#define USE_REGEXP 0
#if USE_REGEXP
QRegExp rx("(\\w+)(?:@/.*/block/.*/)(\\w+)\\W*");
//QRegExp rx("(add|remove|change)@/.*/block/.*/(\\w+)\\W*");
if (rx.indexIn(line) == -1)
return;
action_str = rx.cap(1).toLower();
dev = "/dev/" + rx.cap(2);
#else
if (!line.contains("/block/")) //hotplug
return;
action_str = line.left(line.indexOf('@')).toLower();
dev = "/dev/" + line.right(line.length() - line.lastIndexOf('/') - 1);
#endif //USE_REGEXP
#ifdef __MIPS_LINUX__
}
#endif
QDeviceChangeEvent *event = 0;
if (action_str==QLatin1String("add")) {
emitDeviceAdded(dev);
event = new QDeviceChangeEvent(QDeviceChangeEvent::Add, dev);
} else if (action_str==QLatin1String("remove")) {
emitDeviceRemoved(dev);
event = new QDeviceChangeEvent(QDeviceChangeEvent::Remove, dev);
} else if (action_str==QLatin1String("change")) {
emitDeviceChanged(dev);
event = new QDeviceChangeEvent(QDeviceChangeEvent::Change, dev);
}
//zDebug("%s %s", qPrintable(action_str), qPrintable(dev));
if (event != 0 && !event_receivers.isEmpty()) {
foreach(QObject* obj, event_receivers) {
QCoreApplication::postEvent(obj, event, Qt::HighEventPriority);
}
}
}
#endif //Q_OS_LINUX