1/* This file is part of the KDE project
2 * Copyright (C) 2010 Sebastian Sauer <sebsauer@kdab.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "kaccessiblebridge.h"
21#include "kaccessibleinterface.h"
22
23#include <QAccessibleInterface>
24#include <QWidget>
25#include <QFile>
26#include <QDBusConnection>
27#include <QDBusConnectionInterface>
28#include <QDBusInterface>
29#include <QDBusPendingCall>
30#include <QDBusArgument>
31#include <QDBusMetaType>
32#include <kdebug.h>
33
34Q_EXPORT_PLUGIN(BridgePlugin)
35
36class Bridge::Private
37{
38 public:
39 BridgePlugin *m_plugin;
40 const QString m_key;
41 QAccessibleInterface *m_root;
42 QList<QObject*> m_popupMenus;
43 QRect m_lastFocusRect;
44 QString m_lastFocusName;
45
46 Private(BridgePlugin *plugin, const QString& key)
47 : m_plugin(plugin)
48 , m_key(key)
49 , m_root(0)
50 , m_lastFocusRect(QRect(0,0,0,0))
51 , m_app(0)
52 {
53 }
54
55 ~Private()
56 {
57 delete m_app;
58 }
59
60 QDBusInterface* app()
61 {
62 Q_ASSERT(m_root);
63 if(!m_app) {
64 m_app = new QDBusInterface(QLatin1String("org.kde.kaccessibleapp"), QLatin1String("/Adaptor"));
65 if(m_app->isValid()) {
66 kDebug() << "Connected with the org.kde.kaccessibleapp dbus-service";
67 KAccessibleInterface dbusIface;
68 dbusIface.set(m_root, 0);
69 m_app->asyncCall(QLatin1String("setRootObject"), qVariantFromValue(dbusIface));
70 }
71 }
72 if(m_app->lastError().isValid()) {
73 kDebug() << "DBus error:" << m_app->lastError().name() << m_app->lastError().message();
74 delete m_app; m_app = 0;
75 } else if(!m_app->isValid()) {
76 kDebug() << "Failed to connect with the org.kde.kaccessibleapp dbus-service";
77 delete m_app; m_app = 0;
78 }
79 return m_app;
80 }
81 private:
82 QDBusInterface *m_app;
83};
84Bridge::Bridge(BridgePlugin *plugin, const QString& key)
85 : QObject(plugin)
86 , QAccessibleBridge()
87 , d(new Private(plugin, key))
88{
89}
90
91Bridge::~Bridge()
92{
93 delete d;
94}
95
96void Bridge::notifyAccessibilityUpdate(int reason, QAccessibleInterface *interface, int child)
97{
98 if(reason == QAccessible::ObjectShow || reason == QAccessible::ObjectHide) {
99 return;
100 }
101
102 if(!d->m_root) {
103 return;
104 }
105
106 QObject *obj = interface->object();
107 if(!obj) {
108 return;
109 }
110
111 QDBusInterface* app = d->app();
112 if(!app) {
113 return;
114 }
115
116 const QString name = interface->text(QAccessible::Name, child);
117 const QString description = interface->text(QAccessible::Description, child);
118
119 KAccessibleInterface dbusIface;
120 dbusIface.set(interface, child);
121
122 QAccessibleInterface *childInterface = 0;
123 //if(child > 0) interface->navigate(QAccessible::Child, child, &childInterface);
124
125 switch(reason) {
126 case QAccessible::PopupMenuStart: {
127 d->m_popupMenus.append(obj);
128 } break;
129 case QAccessible::PopupMenuEnd: {
130 const int index = d->m_popupMenus.lastIndexOf(obj);
131 if(index >= 0) d->m_popupMenus.removeAt(index);
132 } break;
133
134 case QAccessible::Alert: {
135 //kDebug() << reasonToString(reason) << "object=" << (obj ? QString("%1 (%2)").arg(obj->objectName()).arg(obj->metaObject()->className()) : "NULL") << "name=" << name;
136 app->asyncCall(QLatin1String( "setAlert" ), qVariantFromValue(dbusIface));
137 } break;
138
139 case QAccessible::DialogStart: {
140 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" )).arg(obj->objectName()).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" ) << name;
141 //app->asyncCall("sayText", name);
142 } break;
143 case QAccessible::DialogEnd: {
144 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" )).arg(obj->objectName()).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" ) << name;
145 //app->asyncCall("sayText", name);
146 } break;
147
148 case QAccessible::NameChanged: {
149 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" )).arg(obj->objectName()).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" ) << name;
150 //app->asyncCall("sayText", name);
151 } break;
152 case QAccessible::ValueChanged: {
153 QString value = interface->text(QAccessible::Value, child);
154 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" )).arg(obj->objectName() ).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" ) << name << QLatin1String( "value=" ) << value;
155 app->asyncCall(QLatin1String( "setValueChanged" ), qVariantFromValue(dbusIface));
156 } break;
157 case QAccessible::StateChanged: {
158 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" ) ).arg(obj->objectName()).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" )<< name << QLatin1String( "state=" ) << stateToString(dbusIface.state);
159 } break;
160
161 case QAccessible::Focus: {
162 // abort if the focus would interrupt a popupmenu
163 if(!d->m_popupMenus.isEmpty()) {
164 bool ok = false;
165 QObject* lastPopupMenu = d->m_popupMenus.last();
166 for(QObject* o = obj; o; o = o->parent())
167 if(o == lastPopupMenu) { ok = true; break; }
168 if(!ok)
169 return;
170 }
171
172 // don't emit the focus changed signal if the focus didn't really changed since last time
173 if(dbusIface.rect == d->m_lastFocusRect && dbusIface.name == d->m_lastFocusName)
174 return;
175 d->m_lastFocusRect = dbusIface.rect;
176 d->m_lastFocusName = dbusIface.name;
177
178 // here we could add hacks to special case applications/widgets :)
179 //
180 // QWidget *w = childInterface ? dynamic_cast<QWidget*>(childobj) : 0;
181 // if(!w) w = dynamic_cast<QWidget*>(obj);
182 // if(w) r = QRect(w->mapToGlobal(QPoint(w->x(), w->y())), w->size());
183
184 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" )).arg(obj->objectName()).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" ) << name << QLatin1String( "rect=" ) << dbusIface.rect;
185 app->asyncCall(QLatin1String( "setFocusChanged" ), qVariantFromValue(dbusIface));
186 } break;
187 default:
188 kDebug() << reasonToString(reason) << QLatin1String( "object=" ) << (obj ? QString(QLatin1String( "%1 (%2)" )).arg(obj->objectName()).arg(QLatin1String( obj->metaObject()->className() )) : QLatin1String( "NULL" )) << QLatin1String( "name=" ) << name;
189 break;
190 }
191
192 delete childInterface;
193 //delete d->m_app; d->m_app = 0;
194}
195
196void Bridge::focusChanged(int px, int py, int rx, int ry, int rwidth, int rheight)
197{
198 kDebug()<<"KAccessibleBridge: focusChanged px=" << px << "py=" << py << "rx=" << rx << "ry=" << ry << "rwidth=" << rwidth << "rheight=" << rheight;
199}
200
201void Bridge::setRootObject(QAccessibleInterface *interface)
202{
203 d->m_root = interface;
204
205 kDebug()<<QLatin1String( "KAccessibleBridge: setRootObject object=" ) << (interface->object() ? QString(QLatin1String( "%1 (%2)" )).arg(interface->object()->objectName()).arg(QLatin1String( interface->object()->metaObject()->className() )) : QLatin1String( "NULL" ));
206
207 if( ! QDBusConnection::sessionBus().isConnected()) {
208 kWarning()<<"KAccessibleBridge: Failed to connect to session bus";
209 d->m_root = 0;
210 return;
211 }
212
213 if( ! QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String( "org.kde.kaccessibleapp" ))) {
214 kDebug()<<"KAccessibleBridge: Starting kaccessibleapp dbus service";
215 QDBusConnection::sessionBus().interface()->startService(QLatin1String( "org.kde.kaccessibleapp" ));
216 if( ! QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String( "org.kde.kaccessibleapp" ))) {
217 kWarning()<<"KAccessibleBridge: Failed to start kaccessibleapp dbus service";
218 d->m_root = 0;
219 return;
220 }
221
222 //for testing;
223 //QDBusConnection::sessionBus().connect("org.kde.kaccessibleapp", "/Adaptor", "org.kde.kaccessibleapp.Adaptor", "focusChanged", this, SLOT(focusChanged(int,int,int,int,int,int)));
224 }
225}
226
227BridgePlugin::BridgePlugin(QObject *parent)
228 : QAccessibleBridgePlugin(parent)
229{
230 qDBusRegisterMetaType<KAccessibleInterface>();
231}
232
233BridgePlugin::~BridgePlugin()
234{
235}
236
237QAccessibleBridge* BridgePlugin::create(const QString &key)
238{
239 return new Bridge(this, key);
240}
241
242QStringList BridgePlugin::keys() const
243{
244 return QStringList() << QLatin1String( "KAccessibleBridge" );
245}
246