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