Plugins are now owned by devices and not by daemon

Plugins can no longer emit networkpackages for *every* device.
Plugins are stored in device, wich selectively loads them.
A Device is needed in order to instantiate a Plugin (via PluginLoader)
PluginLoader is a singleton, because every instance of Device need it.
Added KPluginSelector in the KCM to select the plugins to load.
Added architecture explanation to README

Only PingPlugin is working by now.
This commit is contained in:
Albert Vaca 2013-08-13 05:07:32 +02:00
parent 9b7eecc69d
commit 88fab1f333
18 changed files with 337 additions and 188 deletions

42
README
View file

@ -1,8 +1,41 @@
The kdeconnect protocol:
Class diagram
==============
Backend_1 ... Backend_N
\ | /
Daemon
/ | \
Device_1 ... Device_N
/ \
|-Plugin_1 |-DeviceLink_1
|-Plugin_2 |-DeviceLink_2
|- ... |-...
|-Plugin_N |-DeviceLink_N
Daemon instantiates Backends
Backends manage to create DeviceLinks with the devices they can reach, and Q_EMIT them to Daemon.
When Daemon receives a DeviceLink from a backend it:
- If he already knows the Device, adds the DeviceLink to the Device
- If not, it creates a new Device.
Devices contain a list of DeviceLinks, plus a list of Plugins (instantiated automatically)
Information for and from Plugins is encapsulated in NetworkPackages.
When a DeviceLink receives a NetworkPackage from the device in the other end, Device will notify all the plugins.
When a Plugin wants to send a NetworkPackage, it does so using the pointer to Device
The NetworkPackage format
=========================
Communication between heterogenous devices is achieved using NetworkPackages.
NetworkPackages are independent and self-contained pieces of information that
are sent from one device to another serialized in json.
are sent from one device to another (via a DeviceLink) serialized in json.
The basic structure of a NetworkPackage is the following:
@ -16,5 +49,6 @@ The basic structure of a NetworkPackage is the following:
}
Each type of package defines what it should contain inside its "body", so only
the sender and receiver of this type of package need agree about it.
the emisor Plugin and receiver Plugin of this type of package need agree about
its content.

View file

@ -57,13 +57,7 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
//Debugging
qDebug() << "Starting KdeConnect daemon";
//Load plugins
PluginLoader *loader = new PluginLoader(this);
connect(loader, SIGNAL(pluginLoaded(PackageInterface*)), this, SLOT(pluginLoaded(PackageInterface*)));
loader->loadAllPlugins();
//Load backends (hardcoded by now)
//use: https://techbase.kde.org/Development/Tutorials/Services/Plugins
//Load backends (hardcoded by now, should be plugins in a future)
mLinkProviders.insert(new BroadcastTcpLinkProvider());
//mLinkProviders.insert(new AvahiTcpLinkProvider());
//mLinkProviders.insert(new LoopbackLinkProvider());
@ -77,12 +71,6 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
const QString& name = data.readEntry<QString>("name", defaultName);
Device* device = new Device(id, name);
mDevices[id] = device;
Q_FOREACH (PackageInterface* pr, mPackageInterfaces) {
connect(device, SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
pr, SLOT(receivePackage(const Device&, const NetworkPackage&)));
connect(pr, SIGNAL(sendPackage(const NetworkPackage&)),
device, SLOT(sendPackage(const NetworkPackage&)));
}
}
QNetworkSession* network = new QNetworkSession(QNetworkConfigurationManager().defaultConfiguration());
@ -123,19 +111,6 @@ QStringList Daemon::devices()
return mDevices.keys();
}
void Daemon::pluginLoaded(PackageInterface* packageInterface)
{
qDebug() << "PLUUUUUUUUUUUUUUUUGINLOADEEEEEEEEEEEEEEEEEEEEEEED";
mPackageInterfaces.append(packageInterface);
Q_FOREACH(Device* device, mDevices) {
connect(device, SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
packageInterface, SLOT(receivePackage(const Device&, const NetworkPackage&)));
connect(packageInterface, SIGNAL(sendPackage(const NetworkPackage&)),
device, SLOT(sendPackage(const NetworkPackage&)));
}
}
void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl)
{
const QString& id = identityPackage.get<QString>("deviceId");
@ -166,10 +141,7 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*
Device* device = new Device(id, name, dl);
mDevices[id] = device;
Q_FOREACH (PackageInterface* pr, mPackageInterfaces) {
connect(device, SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
pr, SLOT(receivePackage(const Device&, const NetworkPackage&)));
}
Q_EMIT newDeviceAdded(id);
}

View file

@ -72,19 +72,14 @@ Q_SIGNALS:
private Q_SLOTS:
void onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl);
void pluginLoaded(PackageInterface*);
private:
//Every known device
QMap<QString, Device*> mDevices;
//Different ways to find devices and connect to them
QSet<LinkProvider*> mLinkProviders;
//The classes that send and receive the packages
QVector<PackageInterface*> mPackageInterfaces;
//Every known device
QMap<QString, Device*> mDevices;
};

View file

@ -3,8 +3,14 @@
#include <KSharedPtr>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KStandardDirs>
#include <KPluginSelector>
#include <KServiceTypeTrader>
#include <KPluginInfo>
#include <QDebug>
#include "plugins/pluginloader.h"
#include "devicelinks/devicelink.h"
#include "linkproviders/linkprovider.h"
#include "networkpackage.h"
@ -19,6 +25,7 @@ Device::Device(const QString& id, const QString& name)
//Register in bus
QDBusConnection::sessionBus().registerObject("/modules/kdeconnect/devices/"+id, this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
reloadPlugins();
}
Device::Device(const QString& id, const QString& name, DeviceLink* link)
@ -32,6 +39,8 @@ Device::Device(const QString& id, const QString& name, DeviceLink* link)
QDBusConnection::sessionBus().registerObject("/modules/kdeconnect/devices/"+id, this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
addLink(link);
reloadPlugins();
}
/*
Device::Device(const QString& id, const QString& name, DeviceLink* link)
@ -50,6 +59,43 @@ Device::Device(const QString& id, const QString& name, DeviceLink* link)
QDBusConnection::sessionBus().registerObject("/modules/kdeconnect/Devices/"+id, this);
}
*/
void Device::reloadPlugins()
{
qDeleteAll(m_plugins);
m_plugins.clear();
QString path = KStandardDirs().resourceDirs("config").first()+"kdeconnect/";
QMap<QString,QString> pluginStates = KSharedConfig::openConfig(path + id())->group("Plugins").entryMap();
PluginLoader* loader = PluginLoader::instance();
//Code borrowed from KWin
foreach (const QString& pluginName, loader->getPluginList()) {
const QString value = pluginStates.value(pluginName + QString::fromLatin1("Enabled"), QString());
bool enabled = (value.isNull() ? true : QVariant(value).toBool()); //Enable all plugins by default
qDebug() << pluginName << "enabled:" << enabled;
if (enabled) {
PackageInterface* plugin = loader->instantiatePluginForDevice(pluginName, this);
connect(this, SIGNAL(receivedPackage(const NetworkPackage&)),
plugin, SLOT(receivePackage(const NetworkPackage&)));
// connect(packageInterface, SIGNAL(sendPackage(const NetworkPackage&)),
// device, SLOT(sendPackage(const NetworkPackage&)));
m_plugins.append(plugin);
}
}
}
void Device::setPair(bool b)
{
qDebug() << "setPair" << b;
@ -122,10 +168,10 @@ void Device::privateReceivedPackage(const NetworkPackage& np)
if (np.type() == "kdeconnect.identity" && !m_knownIdentiy) {
m_deviceName = np.get<QString>("deviceName");
} else if (m_paired) {
qDebug() << "package received from paired device";
emit receivedPackage(*this, np);
qDebug() << "package received from trusted device";
Q_EMIT receivedPackage(np);
} else {
qDebug() << "not paired, ignoring package";
qDebug() << "device" << name() << "not trusted, ignoring package" << np.type();
}
}

View file

@ -28,6 +28,7 @@
#include "devicelinks/devicelink.h"
class DeviceLink;
class PackageInterface;
class Device
: public QObject
@ -38,7 +39,6 @@ class Device
Q_PROPERTY(QString name READ name)
public:
//Device known from KConfig, we trust it but we need to wait for a incoming devicelink to communicate
Device(const QString& id, const QString& name);
@ -56,19 +56,20 @@ public:
void addLink(DeviceLink*);
void removeLink(DeviceLink*);
//Send and receive
Q_SIGNALS:
void receivedPackage(const Device& device, const NetworkPackage& np);
public Q_SLOTS:
bool sendPackage(const NetworkPackage& np) const;
//Public dbus operations
public Q_SLOTS:
Q_SCRIPTABLE QStringList availableLinks() const;
Q_SCRIPTABLE bool paired() const { return m_paired; }
Q_SCRIPTABLE bool reachable() const { return !m_deviceLinks.empty(); }
//Send and receive
Q_SIGNALS:
void receivedPackage(const NetworkPackage& np);
public Q_SLOTS:
bool sendPackage(const NetworkPackage& np) const;
//Dbus operations called from kcm
public Q_SLOTS:
Q_SCRIPTABLE void setPair(bool b);
Q_SCRIPTABLE void reloadPlugins();
Q_SCRIPTABLE void sendPing();
Q_SIGNALS:
@ -83,9 +84,12 @@ private:
QString m_deviceId;
QString m_deviceName;
QList<DeviceLink*> m_deviceLinks;
QList<PackageInterface*> m_plugins;
bool m_knownIdentiy;
};
Q_DECLARE_METATYPE(Device*)
#endif // DEVICE_H

View file

@ -1,2 +1,5 @@
install(FILES kdeconnect_package_interface.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR})
add_subdirectory(ping)

View file

@ -20,8 +20,17 @@
#include "packageinterface.h"
PackageInterface::PackageInterface(QObject* parent)
#include <QPointer>
#include "../device.h"
PackageInterface::PackageInterface(QObject* parent, const QVariantList& args)
: QObject(parent)
{
//gcc complains if we don't add something to compile on a class with virtual functions
mDevice = qvariant_cast< Device* >(args.first());
}
Device* PackageInterface::device()
{
return mDevice;
}

View file

@ -22,6 +22,7 @@
#define PACKAGEINTERFACE_H
#include <QObject>
#include <QVariantList>
#include <kdemacros.h>
#include <KPluginFactory>
@ -40,17 +41,18 @@ class KDE_EXPORT PackageInterface
Q_OBJECT
public:
PackageInterface(QObject* parent = 0);
PackageInterface(QObject* parent, const QVariantList& args);
virtual ~PackageInterface() { }
Device* device();
public Q_SLOTS:
//Returns true if it has handled the package in some way
//device.sendPackage can be used to send an answer back to the device
virtual bool receivePackage(const Device& device, const NetworkPackage& np) = 0;
virtual bool receivePackage(const NetworkPackage& np) = 0;
private:
Device* mDevice;
Q_SIGNALS:
//Sends a package to *all* connected devices
void sendPackage(const NetworkPackage& np);
};
#endif

View file

@ -8,11 +8,6 @@ X-KDE-PluginInfo-Email=albertvaka@gmail.com
X-KDE-PluginInfo-Name=kdeconnect_ping
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Website=http://albertvaka.wordpress.com
X-KDE-PluginInfo-Category=Network
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-ParentApp=kdeconnect
X-KDE-Version=4.0
Name=Ping
Comment=Send and receive pings

View file

@ -20,19 +20,20 @@
#include "pingpackageinterface.h"
#include <KDebug>
#include <kicon.h>
#include <KNotification>
#include <KIcon>
#include <QDebug>
K_PLUGIN_FACTORY( KdeConnectPluginFactory, registerPlugin< PingPackageInterface >(); )
K_EXPORT_PLUGIN( KdeConnectPluginFactory("kdeconnect_ping", "kdeconnect_ping") )
PingPackageInterface::PingPackageInterface(QObject* parent, const QVariantList& args)
: PackageInterface(parent)
: PackageInterface(parent, args)
{
Q_UNUSED(args);
qDebug() << "Plugin constructor for device" << device()->name();
}
bool PingPackageInterface::receivePackage(const Device& device, const NetworkPackage& np)
bool PingPackageInterface::receivePackage(const NetworkPackage& np)
{
if (np.type() != PACKAGE_TYPE_PING) return false;
@ -41,7 +42,7 @@ bool PingPackageInterface::receivePackage(const Device& device, const NetworkPac
notification->setPixmap(KIcon("dialog-ok").pixmap(48, 48));
notification->setComponentData(KComponentData("kdeconnect", "kdeconnect"));
notification->setTitle("Ping!");
notification->setText(device.name());
notification->setText(device()->name());
notification->sendEvent();
return true;

View file

@ -21,7 +21,7 @@
#ifndef PINGPACKAGEINTERFACE_H
#define PINGPACKAGEINTERFACE_H
#include <knotification.h>
#include <QObject>
#include "../packageinterface.h"
@ -33,7 +33,8 @@ class KDE_EXPORT PingPackageInterface
public:
explicit PingPackageInterface(QObject *parent, const QVariantList &args);
virtual bool receivePackage(const Device& device, const NetworkPackage& np);
public Q_SLOTS:
virtual bool receivePackage(const NetworkPackage& np);
};

View file

@ -21,49 +21,58 @@
#include "pluginloader.h"
#include "packageinterface.h"
#include "plugins/ping/pingpackageinterface.h"
#include <KServiceTypeTrader>
#include <KDebug>
PluginLoader::PluginLoader(QObject * parent)
: QObject(parent)
#include "../device.h"
PluginLoader* PluginLoader::instance()
{
static PluginLoader* instance = new PluginLoader();
return instance;
}
PluginLoader::~PluginLoader()
PluginLoader::PluginLoader()
{
}
void PluginLoader::loadAllPlugins()
{
kDebug() << "Load all plugins";
KService::List offers = KServiceTypeTrader::self()->query("KdeConnect/Plugin");
qDebug() << "LO TRAIGO DE OFERTA CHACHO" << offers;
KService::List::const_iterator iter;
for(iter = offers.begin(); iter < offers.end(); ++iter)
{
QString error;
for(KService::List::const_iterator iter = offers.begin(); iter < offers.end(); ++iter) {
KService::Ptr service = *iter;
plugins[service->library()] = service;
}
}
QStringList PluginLoader::getPluginList()
{
return plugins.keys();
}
PackageInterface* PluginLoader::instantiatePluginForDevice(QString id, Device* device) {
KService::Ptr service = plugins[id];
if (!service) {
qDebug() << "Plugin unknown" << id;
return NULL;
}
KPluginFactory *factory = KPluginLoader(service->library()).factory();
if (!factory)
{
//KMessageBox::error(0, i18n("<html><p>KPluginFactory could not load the plugin:<br /><i>%1</i></p></html>",
// service->library()));
kError(5001) << "KPluginFactory could not load the plugin:" << service->library();
continue;
if (!factory) {
qDebug() << "KPluginFactory could not load the plugin:" << service->library();
return NULL;
}
PackageInterface *plugin = factory->create<PackageInterface>(this);
QVariant deviceVariant;
deviceVariant.setValue<Device*>(device);
if (plugin) {
kDebug() << "Load plugin:" << service->name();
emit pluginLoaded(plugin);
} else {
kDebug() << error;
}
//FIXME: create<PackageInterface> return NULL
QObject *plugin = factory->create<QObject>(device, QVariantList() << deviceVariant);
if (!plugin) {
qDebug() << "Error loading plugin";
return NULL;
}
qDebug() << "Loaded plugin:" << service->name();
return (PackageInterface*)plugin;
}

View file

@ -22,21 +22,29 @@
#define PACKAGEINTERFACELOADER_H
#include <QObject>
#include <QMap>
#include <QString>
#include "packageinterface.h"
#include <KPluginFactory>
#include <KService>
class PluginLoader : public QObject
class Device;
class PluginLoader
{
Q_OBJECT
public:
PluginLoader(QObject * parent);
virtual ~PluginLoader();
static PluginLoader* instance();
PackageInterface* instantiatePluginForDevice(QString name, Device* device);
QStringList getPluginList();
private:
PluginLoader();
QMap<QString,KService::Ptr> plugins;
void loadAllPlugins();
signals:
void pluginLoaded(PackageInterface * plugin);
};
#endif

View file

@ -25,7 +25,7 @@ target_link_libraries(kcm_kdeconnect
${QT_QTCORE_LIBRARY}
${QT_QTGUI_LIBRARY}
${KDE4_KDEUI_LIBRARY}
${KDE4_KIO_LIBRARY}
${KDE4_KCMUTILS_LIBS}
)
add_dependencies(kcm_kdeconnect

View file

@ -118,7 +118,6 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
}
qDebug() << index.row() << ">= " << m_deviceList.count() << (index.row() >= m_deviceList.count());
if (!index.isValid() || index.row() < 0 || index.row() >= m_deviceList.count()) {
return QVariant();
}

View file

@ -32,6 +32,8 @@
#include <QDBusConnection>
#include <QDBusInterface>
#include <KServiceTypeTrader>
#include <KPluginInfo>
#include <KDebug>
#include <kpluginfactory.h>
#include <kstandarddirs.h>
@ -43,7 +45,8 @@ KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&)
: KCModule(KdeConnectKcmFactory::componentData(), parent)
, kcmUi(new Ui::KdeConnectKcmUi())
, pairedDevicesList(new DevicesModel(this))
, config(KSharedConfig::openConfig("kdeconnectrc"))
, currentDevice(0)
//, config(KSharedConfig::openConfig("kdeconnectrc"))
{
kcmUi->setupUi(this);
@ -52,6 +55,8 @@ KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&)
kcmUi->deviceInfo->setVisible(false);
setButtons(KCModule::NoAdditionalButton);
connect(kcmUi->deviceList, SIGNAL(pressed(QModelIndex)), this, SLOT(deviceSelected(QModelIndex)));
connect(kcmUi->ping_button, SIGNAL(pressed()), this, SLOT(sendPing()));
connect(kcmUi->trust_checkbox,SIGNAL(toggled(bool)), this, SLOT(trustedStateChanged(bool)));
@ -64,27 +69,63 @@ KdeConnectKcm::~KdeConnectKcm()
void KdeConnectKcm::deviceSelected(const QModelIndex& current)
{
//Store previous selection
pluginsConfigChanged();
//FIXME: KPluginSelector has no way to remove a list of plugins and load another, so we need to destroy and recreate it each time
delete kcmUi->pluginSelector;
kcmUi->pluginSelector = new KPluginSelector(this);
kcmUi->verticalLayout_2->addWidget(kcmUi->pluginSelector);
bool valid = current.isValid();
kcmUi->deviceInfo->setVisible(valid);
if (!valid) return;
selectedIndex = current;
bool paired = pairedDevicesList->getDevice(current)->paired();
kcmUi->trust_checkbox->setChecked(paired);
currentDevice = pairedDevicesList->getDevice(current);
kcmUi->deviceName->setText(currentDevice->name());
kcmUi->trust_checkbox->setChecked(currentDevice->paired());
KService::List offers = KServiceTypeTrader::self()->query("KdeConnect/Plugin");
QList<KPluginInfo> scriptinfos = KPluginInfo::fromServices(offers);
QString path = KStandardDirs().resourceDirs("config").first()+"kdeconnect/";
KSharedConfigPtr deviceConfig = KSharedConfig::openConfig(path + currentDevice->id());
kcmUi->pluginSelector->addPlugins(scriptinfos, KPluginSelector::ReadConfigFile, "Plugins", QString(), deviceConfig);
connect(kcmUi->pluginSelector, SIGNAL(changed(bool)), this, SLOT(pluginsConfigChanged()));
}
void KdeConnectKcm::trustedStateChanged(bool b)
{
if (!selectedIndex.isValid()) return;
DeviceDbusInterface* device = pairedDevicesList->getDevice(selectedIndex);
device->setPair(b);
pairedDevicesList->deviceStatusChanged(device->id());
if (!currentDevice) return;
currentDevice->setPair(b);
pairedDevicesList->deviceStatusChanged(currentDevice->id());
}
void KdeConnectKcm::pluginsConfigChanged()
{
//Store previous selection
if (!currentDevice) return;
DeviceDbusInterface* auxCurrentDevice = currentDevice; //HACK to avoid infinite recursion (for some reason calling save on pluginselector emits changed)
currentDevice = 0;
kcmUi->pluginSelector->save();
currentDevice = auxCurrentDevice;
currentDevice->reloadPlugins();
}
void KdeConnectKcm::save()
{
pluginsConfigChanged();
KCModule::save();
}
void KdeConnectKcm::sendPing()
{
if (!selectedIndex.isValid()) return;
pairedDevicesList->getDevice(selectedIndex)->sendPing();
if (!currentDevice) return;
currentDevice->sendPing();
}

View file

@ -30,13 +30,13 @@
#include "wizard.h"
#include "devicesmodel.h"
class Create;
class QModelIndex;
class AccountsModel;
class AccountWidget;
class QStackedLayout;
class QItemSelectionModel;
class QDBusInterface;
class DeviceDbusInterface;
namespace Ui {
class KdeConnectKcmUi;
@ -50,17 +50,21 @@ public:
KdeConnectKcm(QWidget *parent, const QVariantList&);
virtual ~KdeConnectKcm();
private:
virtual void save();
private Q_SLOTS:
void deviceSelected(const QModelIndex& current);
void trustedStateChanged(bool);
void pluginsConfigChanged();
void sendPing();
private:
Ui::KdeConnectKcmUi* kcmUi;
DevicesModel* pairedDevicesList;
AddDeviceWizard* addDeviceWizard;
KSharedConfigPtr config;
QModelIndex selectedIndex;
DeviceDbusInterface* currentDevice;
//KSharedConfigPtr config;
};

View file

@ -45,7 +45,16 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="deviceInfoBorder">
<widget class="QGroupBox" name="deviceInfoBorder_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="deviceInfoBorder">
<item>
<widget class="QGroupBox" name="deviceInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@ -56,30 +65,37 @@
<string/>
</property>
<property name="flat">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="deviceInfo">
<property name="title">
<string/>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="verticalSpacer_2">
<widget class="QLabel" name="deviceName">
<property name="font">
<font>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>215</height>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
@ -110,18 +126,20 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<widget class="KPluginSelector" name="pluginSelector" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>215</height>
</size>
<property name="focusPolicy">
<enum>Qt::WheelFocus</enum>
</property>
</spacer>
</widget>
</item>
</layout>
</widget>
@ -131,6 +149,14 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KPluginSelector</class>
<extends>QWidget</extends>
<header>kpluginselector.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>