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 | |
48 | static UINT cxWindowBorder, cyWindowBorder; |
49 | #endif // Q_WS_WIN |
50 | |
51 | static |
52 | const int minSize = 8; |
53 | |
54 | static |
55 | bool 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 |
64 | static |
65 | void 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) |
104 | static |
105 | bool 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 | |
136 | static |
137 | BOOL CALLBACK getWindowsRecursiveHelper( HWND hwnd, LPARAM lParam ) { |
138 | maybeAddWindow(hwnd, reinterpret_cast< std::vector<QRect>* >(lParam) ); |
139 | return TRUE; |
140 | } |
141 | |
142 | static |
143 | void 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 |
156 | static |
157 | Window 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) |
200 | static |
201 | HWND 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 |
209 | static |
210 | Window 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) |
236 | static |
237 | HWND 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 |
256 | static |
257 | QPixmap 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) |
323 | static |
324 | QPixmap 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 | |
353 | QString WindowGrabber::title; |
354 | QString WindowGrabber::windowClass; |
355 | QPoint WindowGrabber::windowPosition; |
356 | |
357 | WindowGrabber::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 | |
405 | WindowGrabber::~WindowGrabber() |
406 | { |
407 | } |
408 | |
409 | QPixmap 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 | |
471 | void 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 | |
487 | void WindowGrabber::mouseReleaseEvent( QMouseEvent *e ) |
488 | { |
489 | if ( e->button() == Qt::RightButton ) { |
490 | yPos = -1; |
491 | } |
492 | } |
493 | |
494 | static |
495 | const int minDistance = 10; |
496 | |
497 | void 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 | |
517 | void 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. |
531 | void 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. |
545 | void 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. |
558 | int 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 |
569 | void 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 | |