1/*
2 Copyright (C) 2004 Bernd Brandstetter <bbrand@freenet.de>
3 Copyright (C) 2010, 2011 Pau Garcia i Quiles <pgquiles@elpauer.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or ( at your option ) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this library; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "windowgrabber.h"
22
23#include <iostream>
24#include <algorithm>
25
26#include <kwindowinfo.h>
27#include <kdebug.h>
28
29#include <QBitmap>
30#include <QPainter>
31#include <QPixmap>
32#include <QPoint>
33#include <QMouseEvent>
34#include <QWheelEvent>
35
36#ifdef Q_WS_X11
37#include <X11/Xlib.h>
38#include <config-ksnapshot.h>
39#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
40#include <X11/extensions/shape.h>
41#endif // HAVE_X11_EXTENSIONS_SHAPE_H
42#include <QX11Info>
43#endif // Q_WS_X11
44
45#ifdef Q_WS_WIN
46#include <windows.h>
47
48static UINT cxWindowBorder, cyWindowBorder;
49#endif // Q_WS_WIN
50
51static
52const int minSize = 8;
53
54static
55bool operator< ( const QRect& r1, const QRect& r2 )
56{
57 return r1.width() * r1.height() < r2.width() * r2.height();
58}
59
60// Recursively iterates over the window w and its children, thereby building
61// a tree of window descriptors. Windows in non-viewable state or with height
62// or width smaller than minSize will be ignored.
63#ifdef Q_WS_X11
64static
65void getWindowsRecursive( std::vector<QRect> *windows, Window w,
66 int rx = 0, int ry = 0, int depth = 0 )
67{
68 XWindowAttributes atts;
69 XGetWindowAttributes( QX11Info::display(), w, &atts );
70
71 if ( atts.map_state == IsViewable &&
72 atts.width >= minSize && atts.height >= minSize ) {
73 int x = 0, y = 0;
74 if ( depth ) {
75 x = atts.x + rx;
76 y = atts.y + ry;
77 }
78
79 QRect r( x, y, atts.width, atts.height );
80 if ( std::find( windows->begin(), windows->end(), r ) == windows->end() ) {
81 windows->push_back( r );
82 }
83
84 Window root, parent;
85 Window* children;
86 unsigned int nchildren;
87
88 if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) {
89 for( unsigned int i = 0; i < nchildren; ++i ) {
90 getWindowsRecursive( windows, children[ i ], x, y, depth + 1 );
91 }
92
93 if( children != NULL ) {
94 XFree( children );
95 }
96 }
97 }
98
99 if ( depth == 0 ) {
100 std::sort( windows->begin(), windows->end() );
101 }
102}
103#elif defined(Q_WS_WIN)
104static
105bool maybeAddWindow(HWND hwnd, std::vector<QRect> *windows) {
106 WINDOWINFO wi;
107 GetWindowInfo( hwnd, &wi );
108 RECT rect = wi.rcClient;
109
110#if 0
111 RECT rect;
112 GetWindowRect( hwnd, &rect );
113#endif
114
115 int width = rect.right - rect.left;
116 int height = rect.bottom - rect.top;
117
118 // For some reason, rect.left and rect.top are shifted by cxWindowBorders and cyWindowBorders pixels respectively
119 // in *every* case (for every window), but cxWindowBorders and cyWindowBorders are non-zero only in the
120 // biggest-window case, therefore we need to save the biggest cxWindowBorders and cyWindowBorders to adjust the rect later
121 cxWindowBorder = qMax(cxWindowBorder, wi.cxWindowBorders);
122 cyWindowBorder = qMax(cyWindowBorder, wi.cyWindowBorders);
123
124 if ( ( ( wi.dwStyle & WS_VISIBLE ) != 0 ) && (width >= minSize ) && (height >= minSize ) )
125 {
126 //QRect r( rect.left + 4, rect.top + 4, width, height); // 4 = max(wi.cxWindowBorders) = max(wi.cyWindowBorders)
127 QRect r(rect.left + cxWindowBorder, rect.top + cyWindowBorder, width, height);
128 if ( std::find( windows->begin(), windows->end(), r ) == windows->end() ) {
129 windows->push_back( r );
130 return true;
131 }
132 }
133 return false;
134}
135
136static
137BOOL CALLBACK getWindowsRecursiveHelper( HWND hwnd, LPARAM lParam ) {
138 maybeAddWindow(hwnd, reinterpret_cast< std::vector<QRect>* >(lParam) );
139 return TRUE;
140}
141
142static
143void getWindowsRecursive( std::vector<QRect> *windows, HWND hwnd,
144 int rx = 0, int ry = 0, int depth = 0 )
145{
146
147 maybeAddWindow(hwnd, windows);
148
149 EnumChildWindows( hwnd, getWindowsRecursiveHelper, (LPARAM) windows );
150
151 std::sort( windows->begin(), windows->end() );
152}
153#endif // Q_WS_X11
154
155#ifdef Q_WS_X11
156static
157Window findRealWindow( Window w, int depth = 0 )
158{
159 if( depth > 5 ) {
160 return None;
161 }
162
163 static Atom wm_state = XInternAtom( QX11Info::display(), "WM_STATE", False );
164 Atom type;
165 int format;
166 unsigned long nitems, after;
167 unsigned char* prop;
168
169 if( XGetWindowProperty( QX11Info::display(), w, wm_state, 0, 0, False, AnyPropertyType,
170 &type, &format, &nitems, &after, &prop ) == Success ) {
171 if( prop != NULL ) {
172 XFree( prop );
173 }
174
175 if( type != None ) {
176 return w;
177 }
178 }
179
180 Window root, parent;
181 Window* children;
182 unsigned int nchildren;
183 Window ret = None;
184
185 if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) {
186 for( unsigned int i = 0;
187 i < nchildren && ret == None;
188 ++i ) {
189 ret = findRealWindow( children[ i ], depth + 1 );
190 }
191
192 if( children != NULL ) {
193 XFree( children );
194 }
195 }
196
197 return ret;
198}
199#elif defined(Q_WS_WIN)
200static
201HWND findRealWindow( HWND w, int depth = 0 )
202{
203 // TODO Implement
204 return w; // This is WRONG but makes code compile for now
205}
206#endif // Q_WS_X11
207
208#ifdef Q_WS_X11
209static
210Window windowUnderCursor( bool includeDecorations = true )
211{
212 Window root;
213 Window child;
214 uint mask;
215 int rootX, rootY, winX, winY;
216
217 XGrabServer( QX11Info::display() );
218 XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &root, &child,
219 &rootX, &rootY, &winX, &winY, &mask );
220
221 if( child == None ) {
222 child = QX11Info::appRootWindow();
223 }
224
225 if( !includeDecorations ) {
226 Window real_child = findRealWindow( child );
227
228 if( real_child != None ) { // test just in case
229 child = real_child;
230 }
231 }
232
233 return child;
234}
235#elif defined(Q_WS_WIN)
236static
237HWND windowUnderCursor(bool includeDecorations = true)
238{
239 POINT pointCursor;
240 QPoint qpointCursor = QCursor::pos();
241 pointCursor.x = qpointCursor.x();
242 pointCursor.y = qpointCursor.y();
243 HWND windowUnderCursor = WindowFromPoint(pointCursor);
244
245 if( includeDecorations ) {
246 LONG_PTR style = GetWindowLongPtr( windowUnderCursor, GWL_STYLE );
247 if( ( style & WS_CHILD ) != 0 ) {
248 windowUnderCursor = GetAncestor( windowUnderCursor, GA_ROOT );
249 }
250 }
251 return windowUnderCursor;
252}
253#endif
254
255#ifdef Q_WS_X11
256static
257QPixmap grabWindow( Window child, int x, int y, uint w, uint h, uint border,
258 QString *title=0, QString *windowClass=0 )
259{
260 QPixmap pm( QPixmap::grabWindow( QX11Info::appRootWindow(), x, y, w, h ) );
261
262 KWindowInfo winInfo( findRealWindow(child), NET::WMVisibleName, NET::WM2WindowClass );
263
264 if ( title ) {
265 (*title) = winInfo.visibleName();
266 }
267
268 if ( windowClass ) {
269 (*windowClass) = winInfo.windowClassName();
270 }
271
272#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
273 int tmp1, tmp2;
274 //Check whether the extension is available
275 if ( XShapeQueryExtension( QX11Info::display(), &tmp1, &tmp2 ) ) {
276 QBitmap mask( w, h );
277 //As the first step, get the mask from XShape.
278 int count, order;
279 XRectangle* rects = XShapeGetRectangles( QX11Info::display(), child,
280 ShapeBounding, &count, &order );
281 //The ShapeBounding region is the outermost shape of the window;
282 //ShapeBounding - ShapeClipping is defined to be the border.
283 //Since the border area is part of the window, we use bounding
284 // to limit our work region
285 if (rects) {
286 //Create a QRegion from the rectangles describing the bounding mask.
287 QRegion contents;
288 for ( int pos = 0; pos < count; pos++ )
289 contents += QRegion( rects[pos].x, rects[pos].y,
290 rects[pos].width, rects[pos].height );
291 XFree( rects );
292
293 //Create the bounding box.
294 QRegion bbox( 0, 0, w, h );
295
296 if( border > 0 ) {
297 contents.translate( border, border );
298 contents += QRegion( 0, 0, border, h );
299 contents += QRegion( 0, 0, w, border );
300 contents += QRegion( 0, h - border, w, border );
301 contents += QRegion( w - border, 0, border, h );
302 }
303
304 //Get the masked away area.
305 QRegion maskedAway = bbox - contents;
306 QVector<QRect> maskedAwayRects = maskedAway.rects();
307
308 //Construct a bitmap mask from the rectangles
309 QPainter p(&mask);
310 p.fillRect(0, 0, w, h, Qt::color1);
311 for (int pos = 0; pos < maskedAwayRects.count(); pos++)
312 p.fillRect(maskedAwayRects[pos], Qt::color0);
313 p.end();
314
315 pm.setMask(mask);
316 }
317 }
318#endif // HAVE_X11_EXTENSIONS_SHAPE_H
319
320 return pm;
321}
322#elif defined(Q_WS_WIN)
323static
324QPixmap grabWindow( HWND hWnd, QString *title=0, QString *windowClass=0 )
325{
326 RECT windowRect;
327 GetWindowRect(hWnd, &windowRect);
328 int w = windowRect.right - windowRect.left;
329 int h = windowRect.bottom - windowRect.top;
330 HDC targetDC = GetWindowDC(hWnd);
331 HDC hDC = CreateCompatibleDC(targetDC);
332 HBITMAP tempPict = CreateCompatibleBitmap(targetDC, w, h);
333 HGDIOBJ oldPict = SelectObject(hDC, tempPict);
334 BitBlt(hDC, 0, 0, w, h, targetDC, 0, 0, SRCCOPY);
335 tempPict = (HBITMAP) SelectObject(hDC, oldPict);
336 QPixmap pm = QPixmap::fromWinHBITMAP(tempPict);
337
338 DeleteDC(hDC);
339 ReleaseDC(hWnd, targetDC);
340
341 KWindowInfo winInfo( findRealWindow(hWnd), NET::WMVisibleName, NET::WM2WindowClass );
342 if ( title ) {
343 (*title) = winInfo.visibleName();
344 }
345
346 if ( windowClass ) {
347 (*windowClass) = winInfo.windowClassName();
348 }
349 return pm;
350}
351#endif // Q_WS_X11
352
353QString WindowGrabber::title;
354QString WindowGrabber::windowClass;
355QPoint WindowGrabber::windowPosition;
356
357WindowGrabber::WindowGrabber()
358: QDialog( 0, Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint ),
359 current( -1 ), yPos( -1 )
360{
361 setWindowModality( Qt::WindowModal );
362 int y,x;
363 uint w, h;
364
365#ifdef Q_WS_X11
366 uint border, depth;
367 Window root;
368 XGrabServer( QX11Info::display() );
369 Window child = windowUnderCursor();
370 XGetGeometry( QX11Info::display(), child, &root, &x, &y, &w, &h, &border, &depth );
371 XUngrabServer( QX11Info::display() );
372
373 QPixmap pm( grabWindow( child, x, y, w, h, border, &title, &windowClass ) );
374#elif defined(Q_WS_WIN)
375 HWND child = windowUnderCursor();
376
377 WINDOWINFO wi;
378 GetWindowInfo( child, &wi);
379
380 RECT r;
381 GetWindowRect( child, &r);
382 x = r.left;
383 y = r.top;
384 w = r.right - r.left;
385 h = r.bottom - r.top;
386 cxWindowBorder = wi.cxWindowBorders;
387 cyWindowBorder = wi.cyWindowBorders;
388
389 HDC childDC = GetDC(child);
390
391 QPixmap pm( grabWindow( child, &title, &windowClass ) );
392#endif // Q_WS_X11
393
394 getWindowsRecursive( &windows, child );
395
396 QPalette p = palette();
397 p.setBrush( backgroundRole(), QBrush( pm ) );
398 setPalette( p );
399 setFixedSize( pm.size() );
400 setMouseTracking( true );
401 setGeometry( x, y, w, h );
402 current = windowIndex( mapFromGlobal(QCursor::pos()) );
403}
404
405WindowGrabber::~WindowGrabber()
406{
407}
408
409QPixmap WindowGrabber::grabCurrent( bool includeDecorations )
410{
411 int x, y;
412#ifdef Q_WS_X11
413 Window root;
414 uint w, h, border, depth;
415
416 XGrabServer( QX11Info::display() );
417 Window child = windowUnderCursor( includeDecorations );
418 XGetGeometry( QX11Info::display(), child, &root, &x, &y, &w, &h, &border, &depth );
419
420 Window parent;
421 Window* children;
422 unsigned int nchildren;
423
424 if( XQueryTree( QX11Info::display(), child, &root, &parent,
425 &children, &nchildren ) != 0 ) {
426 if( children != NULL ) {
427 XFree( children );
428 }
429
430 int newx, newy;
431 Window dummy;
432
433 if( XTranslateCoordinates( QX11Info::display(), parent, QX11Info::appRootWindow(),
434 x, y, &newx, &newy, &dummy )) {
435 x = newx;
436 y = newy;
437 }
438 }
439
440 windowPosition = QPoint(x,y);
441 QPixmap pm( grabWindow( child, x, y, w, h, border, &title, &windowClass ) );
442 XUngrabServer( QX11Info::display() );
443 return pm;
444#elif defined(Q_WS_WIN)
445 HWND hWindow;
446 hWindow = windowUnderCursor(includeDecorations);
447 Q_ASSERT(hWindow);
448
449 HWND hParent;
450
451// Now find the top-most window
452 do {
453 hParent = hWindow;
454 } while( (hWindow = GetParent(hWindow)) != NULL );
455 Q_ASSERT(hParent);
456
457 RECT r;
458 GetWindowRect(hParent, &r);
459
460 x = r.left;
461 y = r.top;
462
463 windowPosition = QPoint(x,y);
464 QPixmap pm( grabWindow( hParent, &title, &windowClass) );
465 return pm;
466#endif // Q_WS_X11
467 return QPixmap();
468}
469
470
471void WindowGrabber::mousePressEvent( QMouseEvent *e )
472{
473 if ( e->button() == Qt::RightButton ) {
474 yPos = e->globalY();
475 } else {
476 if ( current != -1 ) {
477 windowPosition = e->globalPos() - e->pos() + windows[current].topLeft();
478 emit windowGrabbed( palette().brush( backgroundRole() ).texture().copy( windows[ current ] ) );
479 } else {
480 windowPosition = QPoint(0,0);
481 emit windowGrabbed( QPixmap() );
482 }
483 accept();
484 }
485}
486
487void WindowGrabber::mouseReleaseEvent( QMouseEvent *e )
488{
489 if ( e->button() == Qt::RightButton ) {
490 yPos = -1;
491 }
492}
493
494static
495const int minDistance = 10;
496
497void WindowGrabber::mouseMoveEvent( QMouseEvent *e )
498{
499 if ( yPos == -1 ) {
500 int w = windowIndex( e->pos() );
501 if ( w != -1 && w != current ) {
502 current = w;
503 repaint();
504 }
505 } else {
506 int y = e->globalY();
507 if ( y > yPos + minDistance ) {
508 decreaseScope( e->pos() );
509 yPos = y;
510 } else if ( y < yPos - minDistance ) {
511 increaseScope( e->pos() );
512 yPos = y;
513 }
514 }
515}
516
517void WindowGrabber::wheelEvent( QWheelEvent *e )
518{
519 if ( e->delta() > 0 ) {
520 increaseScope( e->pos() );
521 } else if ( e->delta() < 0 ) {
522 decreaseScope( e->pos() );
523 } else {
524 e->ignore();
525 }
526}
527
528// Increases the scope to the next-bigger window containing the mouse pointer.
529// This method is activated by either rotating the mouse wheel forwards or by
530// dragging the mouse forwards while keeping the right mouse button pressed.
531void WindowGrabber::increaseScope( const QPoint &pos )
532{
533 for ( uint i = current + 1; i < windows.size(); i++ ) {
534 if ( windows[ i ].contains( pos ) ) {
535 current = i;
536 break;
537 }
538 }
539 repaint();
540}
541
542// Decreases the scope to the next-smaller window containing the mouse pointer.
543// This method is activated by either rotating the mouse wheel backwards or by
544// dragging the mouse backwards while keeping the right mouse button pressed.
545void WindowGrabber::decreaseScope( const QPoint &pos )
546{
547 for ( int i = current - 1; i >= 0; i-- ) {
548 if ( windows[ i ].contains( pos ) ) {
549 current = i;
550 break;
551 }
552 }
553 repaint();
554}
555
556// Searches and returns the index of the first (=smallest) window
557// containing the mouse pointer.
558int WindowGrabber::windowIndex( const QPoint &pos ) const
559{
560 for ( uint i = 0; i < windows.size(); i++ ) {
561 if ( windows[ i ].contains( pos ) ) {
562 return i;
563 }
564 }
565 return -1;
566}
567
568// Draws a border around the (child) window currently containing the pointer
569void WindowGrabber::paintEvent( QPaintEvent * )
570{
571 if ( current >= 0 ) {
572 QPainter p;
573 p.begin( this );
574 p.fillRect(rect(), palette().brush( backgroundRole()));
575 p.setPen( QPen( Qt::red, 3 ) );
576 p.drawRect( windows[ current ].adjusted( 0, 0, -1, -1 ) );
577 p.end();
578 }
579}
580
581#include "windowgrabber.moc"
582