From 27b477c284f83ce6b30b72b21dd13c010243b5d0 Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Wed, 27 Apr 2022 10:46:45 +0300 Subject: [PATCH] kinfocenter: reimplement usbview module via libusb now it will function on non-Linux hosts too Signed-off-by: Ivailo Monev --- CMakeLists.txt | 4 +- kinfocenter/Modules/CMakeLists.txt | 10 +- kinfocenter/Modules/usbview/CMakeLists.txt | 30 ++- kinfocenter/Modules/usbview/kcmusb.cpp | 4 +- kinfocenter/Modules/usbview/kcmusb.h | 6 +- kinfocenter/Modules/usbview/usbdevices.cpp | 418 ++++++----------------------- kinfocenter/Modules/usbview/usbdevices.h | 95 +++---- 7 files changed, 143 insertions(+), 424 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e25afbd5..7524ab39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,9 +131,9 @@ set_package_properties(LibKonq PROPERTIES macro_optional_find_package(LibUSB) set_package_properties(LibUSB PROPERTIES DESCRIPTION "User level access to USB devices" - URL "http://libusb.sourceforge.net" + URL "https://libusb.info/" TYPE OPTIONAL - PURPOSE "Provides Logitech mouse support in KControl" + PURPOSE "Provides Logitech mouse support in KControl and USB devices information" ) macro_optional_find_package(PCIUTILS) diff --git a/kinfocenter/Modules/CMakeLists.txt b/kinfocenter/Modules/CMakeLists.txt index 90d6a110..fda95178 100644 --- a/kinfocenter/Modules/CMakeLists.txt +++ b/kinfocenter/Modules/CMakeLists.txt @@ -1,15 +1,17 @@ -add_subdirectory( usbview ) + add_subdirectory( infosummary ) add_subdirectory( memory ) add_subdirectory( devinfo ) add_subdirectory( info ) add_subdirectory( samba ) add_subdirectory( nics ) +add_subdirectory( pci ) add_feature_info("OpenGL support" OPENGL_FOUND "View OpenGL details in kinfocenter." ) add_feature_info("EGL support" OpenGL_EGL_FOUND "View EGL details in kinfocenter." ) add_feature_info("DRM support" LIBDRM_FOUND "View 3D acceleration details in kinfocenter." ) +add_feature_info("USB support" LIBUSB_FOUND "USB details in kinfocenter." ) if((OPENGL_FOUND AND OPENGL_GLU_FOUND) OR OpenGL_EGL_FOUND) add_subdirectory( opengl ) @@ -17,7 +19,11 @@ else() message(STATUS "OpenGL/ES2.0 information module has been disabled.") endif() -add_subdirectory( pci ) +if(LIBUSB_FOUND) + add_subdirectory( usbview ) +else() + message(STATUS "USB information module has been disabled.") +endif() if(RAW1394_FOUND) add_subdirectory( view1394 ) diff --git a/kinfocenter/Modules/usbview/CMakeLists.txt b/kinfocenter/Modules/usbview/CMakeLists.txt index b9e3b581..b1ddd1c0 100644 --- a/kinfocenter/Modules/usbview/CMakeLists.txt +++ b/kinfocenter/Modules/usbview/CMakeLists.txt @@ -1,20 +1,30 @@ - - - ########### next target ############### -set(kcm_usb_PART_SRCS kcmusb.cpp usbdevices.cpp usbdb.cpp ) - +include_directories(${LIBUSB_INCLUDES}) +set(kcm_usb_PART_SRCS kcmusb.cpp usbdevices.cpp usbdb.cpp ) kde4_add_plugin(kcm_usb ${kcm_usb_PART_SRCS}) +target_link_libraries(kcm_usb + ${KDE4_KDEUI_LIBS} + ${QT_QTGUI_LIBRARY} +) -target_link_libraries(kcm_usb ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY}) - -install(TARGETS kcm_usb DESTINATION ${KDE4_PLUGIN_INSTALL_DIR} ) +target_link_libraries(kcm_usb ${LIBUSB_LIBRARIES}) +install( + TARGETS kcm_usb + DESTINATION ${KDE4_PLUGIN_INSTALL_DIR} +) ########### install files ############### -install( FILES kcmusb.desktop DESTINATION ${KDE4_SERVICES_INSTALL_DIR} ) -install( FILES usb.ids DESTINATION ${KDE4_DATA_INSTALL_DIR}/kcmusb ) +install( + FILES kcmusb.desktop + DESTINATION ${KDE4_SERVICES_INSTALL_DIR} +) + +install( + FILES usb.ids + DESTINATION ${KDE4_DATA_INSTALL_DIR}/kcmusb +) diff --git a/kinfocenter/Modules/usbview/kcmusb.cpp b/kinfocenter/Modules/usbview/kcmusb.cpp index 08ed85b2..da839502 100644 --- a/kinfocenter/Modules/usbview/kcmusb.cpp +++ b/kinfocenter/Modules/usbview/kcmusb.cpp @@ -111,9 +111,7 @@ static void delete_recursive(QTreeWidgetItem *item, const QMap new_items; - if (!USBDevice::parse("/proc/bus/usb/devices")) { - USBDevice::parseSys("/sys/bus/usb/devices"); - } + USBDevice::init(); int level = 0; bool found = true; diff --git a/kinfocenter/Modules/usbview/kcmusb.h b/kinfocenter/Modules/usbview/kcmusb.h index 7c4b35ab..195114f4 100644 --- a/kinfocenter/Modules/usbview/kcmusb.h +++ b/kinfocenter/Modules/usbview/kcmusb.h @@ -19,9 +19,9 @@ #include #include -class USBViewer : public KCModule { -Q_OBJECT - +class USBViewer : public KCModule +{ + Q_OBJECT public: explicit USBViewer(QWidget *parent = 0L, const QVariantList &list=QVariantList()); diff --git a/kinfocenter/Modules/usbview/usbdevices.cpp b/kinfocenter/Modules/usbview/usbdevices.cpp index 4df0272b..ed2a639a 100644 --- a/kinfocenter/Modules/usbview/usbdevices.cpp +++ b/kinfocenter/Modules/usbview/usbdevices.cpp @@ -11,130 +11,62 @@ #include "usbdevices.h" #include -#include -#include -#include -#include - -#include -#include -#include #include -#include +#include #include "usbdb.h" -#include - -#if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_DRAGONFLY) -#include -#include -#endif +#include QList USBDevice::_devices; USBDB *USBDevice::_db; -USBDevice::USBDevice() : - _bus(0), _level(0), _parent(0), _port(0), _count(0), _device(0), _channels(0), _power(0), - _speed(0.0), _bwTotal(0), _bwUsed(0), _bwPercent(0), _bwIntr(0), _bwIso(0), _hasBW(false), - _verMajor(0), _verMinor(0), _class(0), _sub(0), _prot(0), _maxPacketSize(0), _configs(0), - _vendorID(0), _prodID(0), _revMajor(0), _revMinor(0) { - _devices.append(this); - - if (!_db) { - _db = new USBDB; - } -} - -USBDevice::~USBDevice() { -} - -static QString catFile(QString fname) { - char buffer[256]; - QString result; - int fd =:: open(QFile::encodeName(fname), O_RDONLY); - if (fd<0) { - return QString(); - } - - if (fd >= 0) { - ssize_t count; - while ((count = ::read(fd, buffer, 256)) > 0) { - result.append(QString(buffer).left(count)); +static float getSpeed(int number) +{ + switch (number) { + case LIBUSB_SPEED_LOW: { + return 1.5; + } + case LIBUSB_SPEED_FULL: { + return 12.0; + } + case LIBUSB_SPEED_HIGH: { + return 480.0; + } + case LIBUSB_SPEED_SUPER: { + return 5000.0; + } + case LIBUSB_SPEED_SUPER_PLUS: { + return 10000.0; + } + case LIBUSB_SPEED_UNKNOWN: { + return 0.0; } - - ::close(fd); } - return result.trimmed(); + kWarning() << "Unknown libusb speed" << number; + return 0.0; } -void USBDevice::parseSysDir(int bus, int parent, int level, const QString& dname) { - _level = level; - _parent = parent; - _manufacturer = catFile(dname + "/manufacturer"); - _product = catFile(dname + "/product"); - - _bus = bus; - _device = catFile(dname + "/devnum").toUInt(); - - if (_device == 1) { - _product += QString(" (%1)").arg(_bus); - } - - _vendorID = catFile(dname + "/idVendor").toUInt(0, 16); - _prodID = catFile(dname + "/idProduct").toUInt(0, 16); - - _class = catFile(dname + "/bDeviceClass").toUInt(0, 16); - _sub = catFile(dname + "/bDeviceSubClass").toUInt(0, 16); - _maxPacketSize = catFile(dname + "/bMaxPacketSize0").toUInt(); - - _speed = catFile(dname + "/speed").toDouble(); - _serial = catFile(dname + "/serial"); - _channels = catFile(dname + "/maxchild").toUInt(); - - double version = catFile(dname + "/version").toDouble(); - _verMajor = int(version); - _verMinor = int(10*(version - floor(version))); - - QDir dir(dname); - dir.setNameFilters(QStringList() << QString("%1-*").arg(bus)); - dir.setFilter(QDir::Dirs); - const QStringList list = dir.entryList(); +static QString getVersion(uint16_t number) +{ + uint8_t *numberarray = reinterpret_cast(&number); + return QString::fromLatin1("%1.%2").arg(QString::number(numberarray[1], 16)).arg(QString::number(numberarray[0], 16)); +} - for (QStringList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { - if ((*it).contains(':')) { - continue; - } +USBDevice::USBDevice() : + _bus(0), _level(0), _parent(0), _port(0), _device(0), _channels(0), + _speed(0.0), _class(0), _sub(0), _prot(0), _maxPacketSize(0), + _vendorID(0), _prodID(0) +{ + _devices.append(this); - USBDevice* dev = new USBDevice(); - dev->parseSysDir(bus, ++level, _device, dname + '/' + *it); + if (!_db) { + _db = new USBDB; } } -void USBDevice::parseLine(const QString& line) { - if (line.startsWith("T:")) { - sscanf(line.toLocal8Bit().data(), "T: Bus=%2d Lev=%2d Prnt=%2d Port=%d Cnt=%2d Dev#=%3d Spd=%3f MxCh=%2d", &_bus, &_level, &_parent, &_port, &_count, &_device, &_speed, &_channels); - } else if (line.startsWith("S: Manufacturer")) { - _manufacturer = line.mid(17); - } else if (line.startsWith("S: Product")) { - _product = line.mid(12); - /* add bus number to root devices */ - if (_device==1) { - _product += QString(" (%1)").arg(_bus); - } - } else if (line.startsWith("S: SerialNumber")) { - _serial = line.mid(17); - } else if (line.startsWith("B:")) { - sscanf(line.toLocal8Bit().data(), "B: Alloc=%3d/%3d us (%2d%%), #Int=%3d, #Iso=%3d", &_bwUsed, &_bwTotal, &_bwPercent, &_bwIntr, &_bwIso); - _hasBW = true; - } else if (line.startsWith("D:")) { - char buffer[11]; - sscanf(line.toLocal8Bit().data(), "D: Ver=%x.%x Cls=%x(%10s) Sub=%x Prot=%x MxPS=%u #Cfgs=%u", &_verMajor, &_verMinor, &_class, buffer, &_sub, &_prot, &_maxPacketSize, &_configs); - _className = buffer; - } else if (line.startsWith("P:")) { - sscanf(line.toLocal8Bit().data(), "P: Vendor=%x ProdID=%x Rev=%x.%x", &_vendorID, &_prodID, &_revMajor, &_revMinor); - } +USBDevice::~USBDevice() { } USBDevice* USBDevice::find(int bus, int device) { @@ -148,9 +80,6 @@ USBDevice* USBDevice::find(int bus, int device) { } QString USBDevice::product() { - if (!_product.isEmpty()) { - return _product; - } QString pname = _db->device(_vendorID, _prodID); if (!pname.isEmpty()) { return pname; @@ -163,9 +92,6 @@ QString USBDevice::dump() { r = "

" + product() + "


"; - if (!_manufacturer.isEmpty()) { - r += i18n("Manufacturer: ") + _manufacturer + "
"; - } if (!_serial.isEmpty()) { r += i18n("Serial #: ") + _serial + "
"; } @@ -190,11 +116,7 @@ QString USBDevice::dump() { pr += "(" + prname +")"; } r += i18n("Protocol%1", pr); -#if !(defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_DRAGONFLY)) - r += ki18n("USB Version%1.%2") - .subs(_verMajor,0,16).subs(_verMinor,2,16,QChar::fromLatin1('0')) - .toString(); -#endif + r += i18n("USB Version%1", _ver); r += ""; QString v = QString::number(_vendorID, 16); @@ -209,248 +131,58 @@ QString USBDevice::dump() { p += "(" + pname +")"; } r += i18n("Product ID0x%1", p); - r += ki18n("Revision%1.%2") - .subs(_revMajor,0,16).subs(_revMinor,2,16,QChar::fromLatin1('0')) - .toString(); + r += i18n("Revision%1", _rev); r += ""; r += i18n("Speed%1 Mbit/s", _speed); r += i18n("Channels%1", _channels); -#if (defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_DRAGONFLY)) && !defined(DISABLE_USBDEVICES_FREEBSD) - if ( _power ) { - r += i18n("Power Consumption%1 mA", _power); - } else { - r += i18n("Power Consumptionself powered"); - r += i18n("Attached Devicenodes%1", _devnodes.at(0)); - if ( _devnodes.count() > 1 ) { - QStringList::const_iterator it = _devnodes.constBegin(); - ++it; - for (; it != _devnodes.constEnd(); ++it ) { - r += "" + *it + ""; - } - } - } -#else r += i18n("Max. Packet Size%1", _maxPacketSize); -#endif r += ""; - if (_hasBW) { - r += i18n("Bandwidth%1 of %2 (%3%)", _bwUsed, _bwTotal, _bwPercent); - r += i18n("Intr. requests%1", _bwIntr); - r += i18n("Isochr. requests%1", _bwIso); - r += ""; - } - r += ""; return r; } -#if !(defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_DRAGONFLY)) -bool USBDevice::parse(const QString &fname) { +bool USBDevice::init() { _devices.clear(); - QString result; - - // read in the complete file - // - // Note: we can't use a QTextStream, as the files in /proc - // are pseudo files with zero length - char buffer[256]; - int fd =:: open(QFile::encodeName(fname), O_RDONLY); - if (fd<0) { - return false; - } - - if (fd >= 0) { - ssize_t count; - while ((count = ::read(fd, buffer, 256)) > 0) { - result.append(QString(buffer).left(count)); - } - - ::close(fd); - } - - // read in the device infos - USBDevice *device = 0; - int start=0, end; - result.replace(QRegExp("^\n"),""); - while ((end = result.indexOf('\n', start)) > 0) { - QString line = result.mid(start, end-start); - - if (line.startsWith("T:")) { - device = new USBDevice(); - } - - if (device) { - device->parseLine(line); - } - - start = end+1; - } - return true; -} - -bool USBDevice::parseSys(const QString &dname) { - QDir d(dname); - d.setNameFilters(QStringList() << "usb*"); - const QStringList list = d.entryList(); - - for (QStringList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { + struct libusb_context *libusbctx = nullptr; + libusb_init(&libusbctx); + struct libusb_device **libusbdevices = nullptr; + const size_t libusbdevicessize = libusb_get_device_list(libusbctx, &libusbdevices); + for (size_t i = 0; i < libusbdevicessize; i++) { USBDevice* device = new USBDevice(); - - int bus = 0; - QRegExp bus_reg("[a-z]*([0-9]+)"); - if (bus_reg.indexIn(*it) != -1) { - bus = bus_reg.cap(1).toInt(); + struct libusb_device_descriptor libusbdevice; + libusb_get_device_descriptor(libusbdevices[i], &libusbdevice); + + device->_bus = libusb_get_bus_number(libusbdevices[i]); + device->_port = libusb_get_port_number(libusbdevices[i]); + device->_speed = getSpeed(libusb_get_device_speed(libusbdevices[i])); + // the maximum is supposed to be 7 + uint8_t portnumbersbuffer[10]; + ::memset(portnumbersbuffer, 0, sizeof(portnumbersbuffer) * sizeof(uint8_t)); + device->_channels = libusb_get_port_numbers(libusbdevices[i], portnumbersbuffer, sizeof(portnumbersbuffer)); + device->_class = libusbdevice.bDeviceClass; + device->_sub = libusbdevice.bDeviceSubClass; + device->_prot = libusbdevice.bDeviceProtocol; + device->_maxPacketSize = libusbdevice.bMaxPacketSize0; + device->_vendorID = libusbdevice.idVendor; + device->_prodID = libusbdevice.idProduct; + device->_serial = QString::number(libusbdevice.iSerialNumber); + device->_ver = getVersion(libusbdevice.bcdUSB); + device->_rev = getVersion(libusbdevice.bcdDevice); + + device->_device = device->_port; + device->_level = 0; + struct libusb_device *libusbparent = libusb_get_parent(libusbdevices[i]); + if (libusbparent) { + device->_parent = libusb_get_port_number(libusbparent); + device->_level = 1; } - - device->parseSysDir(bus, 0, 0, d.absolutePath() + '/' + *it); } + libusb_free_device_list(libusbdevices, 1); + libusb_exit(libusbctx); - return d.count(); -} - -#else - -// Unused by *BSD -bool USBDevice::parseSys(const QString &fname) -{ - Q_UNUSED(fname) - - return true; + return (libusbdevicessize > 0); } - -# if defined(DISABLE_USBDEVICES_FREEBSD) - -/* - * FIXME: The USB subsystem has changed a lot in FreeBSD 8.0 - * Support for it must be written. - */ - -bool USBDevice::parse(const QString &fname) -{ - Q_UNUSED(fname) - - return true; -} - -# else - -/* - * FreeBSD support by Markus Brueffer - * - * Basic idea and some code fragments were taken from FreeBSD's usbdevs(8), - * originally developed for NetBSD, so this code should work with no or - * only little modification on NetBSD. - */ - -void USBDevice::collectData( int fd, int level, usb_device_info &di, int parent) -{ - // determine data for this device - _level = level; - _parent = parent; - - _bus = di.udi_bus; - _device = di.udi_addr; - _product = QLatin1String(di.udi_product); - if ( _device == 1 ) { - _product += ' ' + QString::number( _bus ); - } - _manufacturer = QLatin1String(di.udi_vendor); - _prodID = di.udi_productNo; - _vendorID = di.udi_vendorNo; - _class = di.udi_class; - _sub = di.udi_subclass; - _prot = di.udi_protocol; - _power = di.udi_power; - _channels = di.udi_nports; - - // determine the speed -#if defined(Q_OS_DRAGONFLY) || (defined(Q_OS_FREEBSD) && __FreeBSD_version > 490102) || defined(Q_OS_NETBSD) - switch (di.udi_speed) { - case USB_SPEED_LOW: _speed = 1.5; break; - case USB_SPEED_FULL: _speed = 12.0; break; - case USB_SPEED_HIGH: _speed = 480.0; break; - } -#else - _speed = di.udi_lowspeed ? 1.5 : 12.0; -#endif - - // Get all attached devicenodes - for ( int i = 0; i < USB_MAX_DEVNAMES; ++i ) { - if ( di.udi_devnames[i][0] ) { - _devnodes << di.udi_devnames[i]; - } - } - - // For compatibility, split the revision number - sscanf( di.udi_release, "%x.%x", &_revMajor, &_revMinor ); - - // Cycle through the attached devices if there are any - for ( int p = 0; p < di.udi_nports; ++p ) { - // Get data for device - struct usb_device_info di2; - - di2.udi_addr = di.udi_ports[p]; - - if ( di2.udi_addr >= USB_MAX_DEVICES ) { - continue; - } - - if ( ioctl(fd, USB_DEVICEINFO, &di2) == -1 ) { - continue; - } - - // Only add the device if we didn't detect it, yet - if (!find( di2.udi_bus, di2.udi_addr ) ) { - USBDevice *device = new USBDevice(); - device->collectData( fd, level + 1, di2, di.udi_addr ); - } - } -} - -bool USBDevice::parse(const QString &fname) -{ - Q_UNUSED(fname) - - static bool showErrorMessage = true; - bool error = false; - _devices.clear(); - - QFile controller("/dev/usb0"); - int i = 1; - while ( controller.exists() ) { - // If the devicenode exists, continue with further inspection - if ( controller.open(QIODevice::ReadOnly) ) { - for ( int addr = 1; addr < USB_MAX_DEVICES; ++addr ) { - struct usb_device_info di; - - di.udi_addr = addr; - if ( ioctl(controller.handle(), USB_DEVICEINFO, &di) != -1 ) { - if (!find( di.udi_bus, di.udi_addr ) ) { - USBDevice *device = new USBDevice(); - device->collectData( controller.handle(), 0, di, 0); - } - } - } - controller.close(); -#ifndef Q_OS_NETBSD - } else { - error = true; -#endif - } - controller.setFileName( QString::fromLocal8Bit("/dev/usb%1").arg(i++) ); - } - - if ( showErrorMessage && error ) { - showErrorMessage = false; - KMessageBox::error( 0, i18n("Could not open one or more USB controller. Make sure, you have read access to all USB controllers that should be listed here.")); - } - - return true; -} - -# endif // defined(DISABLE_USBDEVICES_FREEBSD) -#endif // !(defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_DRAGONFLY)) diff --git a/kinfocenter/Modules/usbview/usbdevices.h b/kinfocenter/Modules/usbview/usbdevices.h index e92b50aa..3f2d423a 100644 --- a/kinfocenter/Modules/usbview/usbdevices.h +++ b/kinfocenter/Modules/usbview/usbdevices.h @@ -14,78 +14,51 @@ #include #include -#if defined(Q_OS_DRAGONFLY) -#include -#include -#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) -#include -# if defined(__FreeBSD_version) && __FreeBSD_version >= 800100 -# define DISABLE_USBDEVICES_FREEBSD -# warning "The USB subsystem has changed in 8.0. Disabling." -# else -# include -# include -# endif -#endif - class USBDB; -class USBDevice { +class USBDevice +{ public: - - USBDevice(); - - ~USBDevice(); - - void parseLine(const QString &line); - void parseSysDir(int bus, int parent, int level, const QString &line); - - int level() const { - return _level; - } - int device() const { - return _device; - } - int parent() const { - return _parent; - } - int bus() const { - return _bus; - } - QString product(); - - QString dump(); - - static QList &devices() { - return _devices; - } - static USBDevice *find(int bus, int device); - static bool parse(const QString& fname); - static bool parseSys(const QString& fname); + USBDevice(); + ~USBDevice(); + + int level() const { + return _level; + } + int device() const { + return _device; + } + int parent() const { + return _parent; + } + int bus() const { + return _bus; + } + QString product(); + + QString dump(); + + static QList &devices() { + return _devices; + } + static USBDevice *find(int bus, int device); + static bool init(); private: + static QList _devices; - static QList _devices; - - static USBDB *_db; + static USBDB *_db; - int _bus, _level, _parent, _port, _count, _device, _channels, _power; - float _speed; + int _bus, _level, _parent, _port, _device, _channels; + float _speed; - QString _manufacturer, _product, _serial; + QString _serial; - int _bwTotal, _bwUsed, _bwPercent, _bwIntr, _bwIso; - bool _hasBW; + unsigned int _class, _sub, _prot, _maxPacketSize; - unsigned int _verMajor, _verMinor, _class, _sub, _prot, _maxPacketSize, _configs; - QString _className; + unsigned int _vendorID, _prodID; - unsigned int _vendorID, _prodID, _revMajor, _revMinor; - -#if (defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)) && !defined(DISABLE_USBDEVICES_FREEBSD) - void collectData( int fd, int level, usb_device_info &di, int parent ); - QStringList _devnodes; -#endif + QString _ver, _rev; }; #endif -- 2.11.0