1 /*
2 skypewindow.cpp - Kopete Skype Protocol
3
4 Copyright (c) 2009 by Pali Rohár <pali.rohar@gmail.com>
5
6 *************************************************************************
7 * *
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public *
10 * License as published by the Free Software Foundation; either *
11 * version 2 of the License, or (at your option) any later version. *
12 * *
13 *************************************************************************
14 */
15
16 #include "skypewindow.h"
17
18 #include <QString>
19 #include <QRegExp>
20 #include <QHash>
21 #include <QX11Info>
22 #include <QList>
23 #include <QTimer>
24 #include <QEventLoop>
25 #include <QWaitCondition>
26 #include <QMutex>
27
28 #include <KWindowSystem>
29 #include <KWindowInfo>
30 #include <NET>
31 #include <KDebug>
32
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35
36 class SkypeWindowPrivate
37 {
38 public:
SkypeWindowPrivate()39 SkypeWindowPrivate() {
40 pid = 0;
41 searchWindowsFoundWId = 0;
42 searchWindows = false;
43
44 }
45 Q_PID pid;
46 WId searchWindowsFoundWId;
47 QString searchWindowsUser;
48 bool searchWindows;
49 QHash <const QString, WId> hiddenWindows;
50 QHash <WId, WId> webcamStreams;
51 };
52
SkypeWindow(Q_PID pid)53 SkypeWindow::SkypeWindow(Q_PID pid) {
54 kDebug(SKYPE_DEBUG_GLOBAL) << pid;
55 d = new SkypeWindowPrivate;
56 d->pid = pid;
57 connect( KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(windowAdded(WId)) );
58 }
59
~SkypeWindow()60 SkypeWindow::~SkypeWindow() {
61 kDebug(SKYPE_DEBUG_GLOBAL);
62 disconnect( KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(windowAdded(WId)) );
63 //TODO: revert back all webcam streams and delete or show hidden dialogs if it is needed
64 if ( ! d->hiddenWindows.isEmpty() ) {
65 kDebug(SKYPE_DEBUG_GLOBAL) << "We have" << d->hiddenWindows.size() << "hidden dialogs";
66 }
67 delete d;
68 }
69
isCallDialog(const QString & user,WId wid)70 bool SkypeWindow::isCallDialog(const QString &user, WId wid) {
71 kDebug(SKYPE_DEBUG_GLOBAL) << user << wid;
72
73 Atom atom = XInternAtom(QX11Info::display(), "_NET_WM_PID", True); //get atom properties for process id
74 Atom actual_type; int actual_format; unsigned long nitems, bytes_after; //do not use
75 unsigned char *prop; //array of return value of pid
76 int status = XGetWindowProperty(QX11Info::display(), wid, atom, 0, 1024, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop); //try get process id from window id
77
78 Q_PID pid = 0;
79 if ( status == 0 ) {
80 if ( prop ) { //if all ok, we have process id in prop
81 pid = prop[1] * 256;
82 pid += prop[0];
83 }
84 XFree(prop); //free memory
85 }
86
87 if ( d->pid == 0 || d->pid == pid ) { //check if process id of window id is same as in constructor
88 QString userExp = user;
89 if ( user.startsWith("+") ) { //check if it is phone number, it may start with "+"
90 userExp.insert(0, "\\");
91 }
92 //english regular exp for skype call dialog
93 QString regExp1 = QString::fromUtf8("^Call with %1$").arg(userExp);
94 QString regExp2 = QString::fromUtf8("^[0-9]{2}:[0-9]{2} \\| Call with %1$").arg(userExp);
95 QString regExp3 = QString::fromUtf8("^Call Finished \\| Call with %1$").arg(userExp);
96 QString regExpSingleConference = QString::fromUtf8("^Conference Call - 1 participants$");
97 QString windowName = KWindowSystem::windowInfo(wid, NET::WMName | NET::WMVisibleName).name(); //get window title from window id
98
99 if ( QRegExp(regExpSingleConference).exactMatch(windowName) ) { //oups, sometime get skype conference call with one user and after 100ms skype fix, so try again get window title name
100 int count = 10;
101 while ( count >= 0 && QRegExp(regExpSingleConference).exactMatch(windowName) ) {
102 kDebug(SKYPE_DEBUG_GLOBAL) << "Found Conference Call, waiting if it skype change";
103 QWaitCondition sleep; QMutex mutex; sleep.wait(&mutex, 50);
104 windowName = KWindowSystem::windowInfo(wid, NET::WMName | NET::WMVisibleName).name();
105 --count;
106 }
107 }
108
109 if ( QRegExp(regExp1).exactMatch(windowName) || QRegExp(regExp2).exactMatch(windowName) || QRegExp(regExp3).exactMatch(windowName) ) { //check if pass to english dialog title with user name
110 kDebug(SKYPE_DEBUG_GLOBAL) << "It is skype dialog";
111 return true;
112 } else {
113 kDebug(SKYPE_DEBUG_GLOBAL) << "pid" << pid << "windowName" << windowName << "is not skype call dialog for name" << user;
114 }
115 } else {
116 kDebug(SKYPE_DEBUG_GLOBAL) << "pid" << pid << "is not skype call dialog for name" << user;
117 }
118
119 return false;
120 }
121
windowAdded(WId wid)122 void SkypeWindow::windowAdded(WId wid) {
123 kDebug(SKYPE_DEBUG_GLOBAL) << wid;
124 if ( d->searchWindows ) {
125 if ( ! isCallDialog(d->searchWindowsUser, wid) )
126 return;
127 d->searchWindowsFoundWId = wid;
128 emit(foundCallDialog());
129 }
130 if ( ! d->hiddenWindows.key(wid, QString()).isEmpty() ) {
131 kDebug(SKYPE_DEBUG_GLOBAL) << "Skype call dialog apper again, hide it id" << wid;
132 XUnmapWindow(QX11Info::display(), wid); //hide it again
133 }
134 }
135
getCallDialogWId(const QString & user)136 WId SkypeWindow::getCallDialogWId(const QString &user) {
137 kDebug(SKYPE_DEBUG_GLOBAL) << user;
138 WId wid = d->hiddenWindows.value(user, 0); //first check if it not hidded
139 if ( wid != 0 && isCallDialog(user, wid) ) //check if this this window still exists
140 return wid;
141 else
142 d->hiddenWindows.remove(user); //if no delete it from list
143 QList<WId>::ConstIterator it;
144 for ( it = KWindowSystem::windows().begin(); it != KWindowSystem::windows().end(); ++it ) { //get all active windows id
145 if ( isCallDialog(user, *it) ) {
146 kDebug(SKYPE_DEBUG_GLOBAL) << "Found skype call dialog WId" << *it;
147 return *it;
148 }
149 }
150 d->searchWindowsUser = user;
151 d->searchWindowsFoundWId = 0;
152 d->searchWindows = true;
153 QEventLoop * loop = new QEventLoop;
154 connect( this, SIGNAL(foundCallDialog()), loop, SLOT(quit()) );
155 QTimer::singleShot( 1000, loop, SLOT(quit()) );
156 loop->exec();
157 disconnect( this, SIGNAL(foundCallDialog()), loop, SLOT(quit()) );
158 delete loop;
159 wid = d->searchWindowsFoundWId;
160 d->searchWindowsUser.clear();
161 d->searchWindowsFoundWId = 0;
162 d->searchWindows = false;
163
164 if ( wid != 0 ) {
165 kDebug(SKYPE_DEBUG_GLOBAL) << "Found skype call dialog WId" << wid;
166 return wid;
167 }
168 return 0; //We do not find it
169 }
170
hideCallDialog(const QString & user)171 void SkypeWindow::hideCallDialog(const QString &user) {
172 kDebug(SKYPE_DEBUG_GLOBAL) << user;
173 WId wid = getCallDialogWId(user);
174 if ( wid == 0 ) {
175 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find WId of skype call dialog";
176 return;
177 }
178 kDebug(SKYPE_DEBUG_GLOBAL) << "Hide skype call dialog id" << wid;
179 XUnmapWindow(QX11Info::display(), wid); //window wid passed test for reg exp, maybe it is skype call dialog, hide it now
180 d->hiddenWindows.insert(user, wid);
181 }
182
showCallDialog(const QString & user)183 void SkypeWindow::showCallDialog(const QString &user) {
184 kDebug(SKYPE_DEBUG_GLOBAL) << user;
185 WId wid = d->hiddenWindows.value(user, 0);
186 if ( wid == 0 ) {
187 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find WId of skype call dialog, maybe now it does not exist";
188 return;
189 }
190 kDebug(SKYPE_DEBUG_GLOBAL) << "Show skype call dialog WId" << wid;
191 XMapWindow(QX11Info::display(), wid); //map this window
192 KWindowSystem::activateWindow(wid); //active this window in kde
193 d->hiddenWindows.remove(user);
194 }
195
deleteCallDialog(const QString & user)196 void SkypeWindow::deleteCallDialog(const QString &user) {
197 kDebug(SKYPE_DEBUG_GLOBAL) << user;
198 WId wid = d->hiddenWindows.value(user, 0);
199 if ( wid == 0 ) {
200 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find WId of skype call dialog, maybe it is now deleted";
201 return;
202 }
203 kDebug(SKYPE_DEBUG_GLOBAL) << "Delete skype call dialog id" << wid;
204 XDestroyWindow(QX11Info::display(), wid); //permanently destroy this window and all subwindows
205 d->hiddenWindows.remove(user);
206 }
207
isWebcamWidget(WId wid)208 bool SkypeWindow::isWebcamWidget(WId wid) {
209 kDebug(SKYPE_DEBUG_GLOBAL) << wid;
210 XWindowAttributes window_attributes; //window attributes
211 int status = XGetWindowAttributes(QX11Info::display(), wid, &window_attributes); //get attributes from window wid
212 kDebug(SKYPE_DEBUG_GLOBAL) << "Attributes: width =" << window_attributes.width << "height =" << window_attributes.height << "status =" << status;
213 if ( status != 0 && window_attributes.width == 320 && window_attributes.height == 240 ) { //if all is ok and width is 320 and height is 240 it may be webcam window
214 kDebug(SKYPE_DEBUG_GLOBAL) << "It is webcam widget";
215 return true;
216 }
217 return false;
218 }
219
getWebcamWidgetWId(WId actualWId)220 WId SkypeWindow::getWebcamWidgetWId(WId actualWId) {
221 kDebug(SKYPE_DEBUG_GLOBAL) << actualWId;
222 if ( isWebcamWidget(actualWId) ) //first check if it is webcam widget
223 return actualWId;
224 Window root, parent; //do not use
225 Window * children; //array of all children windows
226 unsigned int nchildren; //count children windows
227 int status = XQueryTree(QX11Info::display(), actualWId, &root, &parent, &children, &nchildren); //get all children windows
228 if ( status == 0 ) {
229 kDebug(SKYPE_DEBUG_GLOBAL) << "Cant get children windows";
230 return 0;
231 }
232 for ( unsigned int i=0; i<nchildren; ++i ) { //browse all child windows and try find webcam widget
233 WId newWId = getWebcamWidgetWId(children[i]); //try get windows id of child i
234 if ( newWId != 0 ) {
235 XFree(children); //free memory
236 return newWId;
237 }
238 }
239 XFree(children); //free memory
240 return 0;
241 }
242
moveWebcamWidget(const QString & user,WId otherWId,int x,int y)243 void SkypeWindow::moveWebcamWidget(const QString &user, WId otherWId, int x, int y) {
244 kDebug(SKYPE_DEBUG_GLOBAL) << user << otherWId << x << y;
245 WId callDialogWId = getCallDialogWId(user);
246 if ( callDialogWId == 0 ) {
247 kDebug(SKYPE_DEBUG_GLOBAL) << "Cant find WId of skype call dialog";
248 return;
249 }
250 WId webcamWidgetWId = getWebcamWidgetWId(callDialogWId);
251 if ( webcamWidgetWId == 0 ) {
252 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find WId of skype webcam widget, maybe it is not an incoming webcam stream";
253 return;
254 }
255 //get parent of webcam stream widget
256 Window root, parent;
257 Window * children;
258 unsigned int nchildren;
259 int status = XQueryTree(QX11Info::display(), webcamWidgetWId, &root, &parent, &children, &nchildren);
260 if ( status == 0 ) {
261 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find parent of skype webcam widget";
262 return;
263 }
264 XFree(children);
265 d->webcamStreams.insert(webcamWidgetWId, parent);
266 XReparentWindow(QX11Info::display(), webcamWidgetWId, otherWId, x, y); //move webcam widget to other window at position x, y
267 XMapWindow(QX11Info::display(), webcamWidgetWId); //map this window to show webcam stream
268 //TODO: disable right mouse click events in window webcamWidgetWId
269 }
270
revertWebcamWidget(const QString & user)271 void SkypeWindow::revertWebcamWidget(const QString &user) {
272 kDebug(SKYPE_DEBUG_GLOBAL) << user;
273 WId callDialogWId = getCallDialogWId(user);
274 if ( callDialogWId == 0 ) {
275 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find WId of skype call dialog";
276 return;
277 }
278 WId webcamWidgetWId = getWebcamWidgetWId(callDialogWId);
279 if ( callDialogWId == 0 ) {
280 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find WId of skype webcam widget, maybe it is not an incoming webcam stream";
281 return;
282 }
283 WId parentWId = d->webcamStreams.value(webcamWidgetWId, 0);
284 if ( parentWId == 0 ) {
285 kDebug(SKYPE_DEBUG_GLOBAL) << "Cannot find parent of skype webcam widget";
286 return;
287 }
288 d->webcamStreams.remove(webcamWidgetWId);
289 XReparentWindow(QX11Info::display(), webcamWidgetWId, parentWId, 6+1+3+2, 6+1+3+24); //Fix correct position, it is +2+24? (6+1+3 is frame size of skype client call dialog, +2+24 is position of webcam stream)
290 XUnmapWindow(QX11Info::display(), webcamWidgetWId);
291 }
292
293