OSDN Git Service

280455179e10ba7108c1e660de09ac3f0311b460
[kde/kde-extraapps.git] / ksnapshot / windowgrabber.cpp
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 <QPen>
34 #include <QtGui/qevent.h>
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 static
46 const int minSize = 8;
47
48 QT_BEGIN_NAMESPACE
49 static inline bool operator< ( const QRect& r1, const QRect& r2 )
50 {
51     return r1.width() * r1.height() < r2.width() * r2.height();
52 }
53 QT_END_NAMESPACE
54
55 // Recursively iterates over the window w and its children, thereby building
56 // a tree of window descriptors. Windows in non-viewable state or with height
57 // or width smaller than minSize will be ignored.
58 #ifdef Q_WS_X11
59 static
60 void getWindowsRecursive( std::vector<QRect> *windows, Window w,
61               int rx = 0, int ry = 0, int depth = 0 )
62 {
63     XWindowAttributes atts;
64     XGetWindowAttributes( QX11Info::display(), w, &atts );
65
66     if ( atts.map_state == IsViewable &&
67          atts.width >= minSize && atts.height >= minSize ) {
68         int x = 0, y = 0;
69         if ( depth ) {
70             x = atts.x + rx;
71             y = atts.y + ry;
72         }
73
74         QRect r( x, y, atts.width, atts.height );
75         if ( std::find( windows->begin(), windows->end(), r ) == windows->end() ) {
76             windows->push_back( r );
77         }
78
79         Window root, parent;
80         Window* children;
81         unsigned int nchildren;
82
83         if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) {
84                 for( unsigned int i = 0; i < nchildren; ++i ) {
85                     getWindowsRecursive( windows, children[ i ], x, y, depth + 1 );
86                 }
87
88                 if( children != NULL ) {
89                     XFree( children );
90                 }
91         }
92     }
93
94     if ( depth == 0 ) {
95         std::sort( windows->begin(), windows->end() );
96     }
97 }
98
99 static
100 Window findRealWindow( Window w, int depth = 0 )
101 {
102     if( depth > 5 ) {
103         return None;
104     }
105
106     static Atom wm_state = XInternAtom( QX11Info::display(), "WM_STATE", False );
107     Atom type;
108     int format;
109     unsigned long nitems, after;
110     unsigned char* prop;
111
112     if( XGetWindowProperty( QX11Info::display(), w, wm_state, 0, 0, False, AnyPropertyType,
113                             &type, &format, &nitems, &after, &prop ) == Success ) {
114         if( prop != NULL ) {
115             XFree( prop );
116         }
117
118         if( type != None ) {
119             return w;
120         }
121     }
122
123     Window root, parent;
124     Window* children;
125     unsigned int nchildren;
126     Window ret = None;
127
128     if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) {
129         for( unsigned int i = 0;
130              i < nchildren && ret == None;
131              ++i ) {
132             ret = findRealWindow( children[ i ], depth + 1 );
133         }
134
135         if( children != NULL ) {
136             XFree( children );
137         }
138     }
139
140     return ret;
141 }
142
143 static
144 Window windowUnderCursor( bool includeDecorations = true )
145 {
146     Window root;
147     Window child;
148     uint mask;
149     int rootX, rootY, winX, winY;
150
151     XGrabServer( QX11Info::display() );
152     XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &root, &child,
153            &rootX, &rootY, &winX, &winY, &mask );
154
155     if( child == None ) {
156         child = QX11Info::appRootWindow();
157     }
158
159     if( !includeDecorations ) {
160         Window real_child = findRealWindow( child );
161
162         if( real_child != None ) { // test just in case
163             child = real_child;
164         }
165     }
166
167     return child;
168 }
169
170 static
171 QPixmap grabWindow( Window child, int x, int y, uint w, uint h, uint border,
172             QString *title=0, QString *windowClass=0 )
173 {
174     QPixmap pm( QPixmap::grabWindow( QX11Info::appRootWindow(), x, y, w, h ) );
175
176     KWindowInfo winInfo( findRealWindow(child), NET::WMVisibleName, NET::WM2WindowClass );
177
178     if ( title ) {
179         (*title) = winInfo.visibleName();
180     }
181
182     if ( windowClass ) {
183         (*windowClass) = winInfo.windowClassName();
184     }
185
186 #ifdef HAVE_X11_EXTENSIONS_SHAPE_H
187     int tmp1, tmp2;
188     //Check whether the extension is available
189     if ( XShapeQueryExtension( QX11Info::display(), &tmp1, &tmp2 ) ) {
190     QBitmap mask( w, h );
191     //As the first step, get the mask from XShape.
192     int count, order;
193     XRectangle* rects = XShapeGetRectangles( QX11Info::display(), child,
194                          ShapeBounding, &count, &order );
195     //The ShapeBounding region is the outermost shape of the window;
196     //ShapeBounding - ShapeClipping is defined to be the border.
197     //Since the border area is part of the window, we use bounding
198     // to limit our work region
199     if (rects) {
200         //Create a QRegion from the rectangles describing the bounding mask.
201         QRegion contents;
202         for ( int pos = 0; pos < count; pos++ )
203         contents += QRegion( rects[pos].x, rects[pos].y,
204                      rects[pos].width, rects[pos].height );
205         XFree( rects );
206
207         //Create the bounding box.
208         QRegion bbox( 0, 0, w, h );
209
210         if( border > 0 ) {
211             contents.translate( border, border );
212             contents += QRegion( 0, 0, border, h );
213             contents += QRegion( 0, 0, w, border );
214             contents += QRegion( 0, h - border, w, border );
215             contents += QRegion( w - border, 0, border, h );
216         }
217
218         //Get the masked away area.
219         QRegion maskedAway = bbox - contents;
220         QVector<QRect> maskedAwayRects = maskedAway.rects();
221
222         //Construct a bitmap mask from the rectangles
223         QPainter p(&mask);
224         p.fillRect(0, 0, w, h, Qt::color1);
225         for (int pos = 0; pos < maskedAwayRects.count(); pos++)
226             p.fillRect(maskedAwayRects[pos], Qt::color0);
227         p.end();
228
229         pm.setMask(mask);
230     }
231     }
232 #endif // HAVE_X11_EXTENSIONS_SHAPE_H
233
234     return pm;
235 }
236 #endif // Q_WS_X11
237
238 QString WindowGrabber::title;
239 QString WindowGrabber::windowClass;
240 QPoint WindowGrabber::windowPosition;
241
242 WindowGrabber::WindowGrabber()
243 : QDialog( 0, Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint ),
244   current( -1 ), yPos( -1 )
245 {
246     setWindowModality( Qt::WindowModal );
247     int y,x;
248     uint w, h;
249
250 #ifdef Q_WS_X11
251     uint border, depth;
252     Window root;
253     XGrabServer( QX11Info::display() );
254     Window child = windowUnderCursor();
255     XGetGeometry( QX11Info::display(), child, &root, &x, &y, &w, &h, &border, &depth );
256     XUngrabServer( QX11Info::display() );
257
258     QPixmap pm( grabWindow( child, x, y, w, h, border, &title, &windowClass ) );
259 #endif // Q_WS_X11
260
261     getWindowsRecursive( &windows, child );
262
263     QPalette p = palette();
264     p.setBrush( backgroundRole(), QBrush( pm ) );
265     setPalette( p );
266     setFixedSize( pm.size() );
267     setMouseTracking( true );
268     setGeometry( x, y, w, h );
269     current = windowIndex( mapFromGlobal(QCursor::pos()) );
270 }
271
272 WindowGrabber::~WindowGrabber()
273 {
274 }
275
276 QPixmap WindowGrabber::grabCurrent( bool includeDecorations )
277 {
278     int x, y;
279 #ifdef Q_WS_X11
280     Window root;
281     uint w, h, border, depth;
282
283     XGrabServer( QX11Info::display() );
284     Window child = windowUnderCursor( includeDecorations );
285     XGetGeometry( QX11Info::display(), child, &root, &x, &y, &w, &h, &border, &depth );
286
287     Window parent;
288     Window* children;
289     unsigned int nchildren;
290
291     if( XQueryTree( QX11Info::display(), child, &root, &parent,
292                     &children, &nchildren ) != 0 ) {
293         if( children != NULL ) {
294             XFree( children );
295         }
296
297         int newx, newy;
298         Window dummy;
299
300         if( XTranslateCoordinates( QX11Info::display(), parent, QX11Info::appRootWindow(),
301             x, y, &newx, &newy, &dummy )) {
302             x = newx;
303             y = newy;
304         }
305     }
306
307     windowPosition = QPoint(x,y);
308     QPixmap pm( grabWindow( child, x, y, w, h, border, &title, &windowClass ) );
309     XUngrabServer( QX11Info::display() );
310     return pm;
311 #endif // Q_WS_X11
312     return QPixmap();
313 }
314
315
316 void WindowGrabber::mousePressEvent( QMouseEvent *e )
317 {
318     if ( e->button() == Qt::RightButton ) {
319         yPos = e->globalY();
320     } else {
321         if ( current != -1 ) {
322             windowPosition = e->globalPos() - e->pos() + windows[current].topLeft();
323             emit windowGrabbed( palette().brush( backgroundRole() ).texture().copy( windows[ current ] ) );
324         } else {
325             windowPosition = QPoint(0,0);
326             emit windowGrabbed( QPixmap() );
327         }
328         accept();
329     }
330 }
331
332 void WindowGrabber::mouseReleaseEvent( QMouseEvent *e )
333 {
334     if ( e->button() == Qt::RightButton ) {
335         yPos = -1;
336     }
337 }
338
339 static
340 const int minDistance = 10;
341
342 void WindowGrabber::mouseMoveEvent( QMouseEvent *e )
343 {
344     if ( yPos == -1 ) {
345         int w = windowIndex( e->pos() );
346         if ( w != -1 && w != current ) {
347             current = w;
348             repaint();
349         }
350     } else {
351         int y = e->globalY();
352         if ( y > yPos + minDistance ) {
353             decreaseScope( e->pos() );
354             yPos = y;
355         } else if ( y < yPos - minDistance ) {
356             increaseScope( e->pos() );
357             yPos = y;
358         }
359     }
360 }
361
362 void WindowGrabber::wheelEvent( QWheelEvent *e )
363 {
364     if ( e->delta() > 0 ) {
365         increaseScope( e->pos() );
366     } else if ( e->delta() < 0 ) {
367         decreaseScope( e->pos() );
368     } else {
369         e->ignore();
370     }
371 }
372
373 // Increases the scope to the next-bigger window containing the mouse pointer.
374 // This method is activated by either rotating the mouse wheel forwards or by
375 // dragging the mouse forwards while keeping the right mouse button pressed.
376 void WindowGrabber::increaseScope( const QPoint &pos )
377 {
378     for ( uint i = current + 1; i < windows.size(); i++ ) {
379         if ( windows[ i ].contains( pos ) ) {
380             current = i;
381             break;
382         }
383     }
384     repaint();
385 }
386
387 // Decreases the scope to the next-smaller window containing the mouse pointer.
388 // This method is activated by either rotating the mouse wheel backwards or by
389 // dragging the mouse backwards while keeping the right mouse button pressed.
390 void WindowGrabber::decreaseScope( const QPoint &pos )
391 {
392     for ( int i = current - 1; i >= 0; i-- ) {
393     if ( windows[ i ].contains( pos ) ) {
394         current = i;
395         break;
396     }
397     }
398     repaint();
399 }
400
401 // Searches and returns the index of the first (=smallest) window
402 // containing the mouse pointer.
403 int WindowGrabber::windowIndex( const QPoint &pos ) const
404 {
405     for ( uint i = 0; i < windows.size(); i++ ) {
406         if ( windows[ i ].contains( pos ) ) {
407             return i;
408         }
409     }
410     return -1;
411 }
412
413 // Draws a border around the (child) window currently containing the pointer
414 void WindowGrabber::paintEvent( QPaintEvent * )
415 {
416     if ( current >= 0 ) {
417         QPainter p;
418         p.begin( this );
419         p.fillRect(rect(), palette().brush( backgroundRole()));
420         p.setPen( QPen( Qt::red, 3 ) );
421         p.drawRect( windows[ current ].adjusted( 0, 0, -1, -1 ) );
422         p.end();
423     }
424 }
425
426 #include "moc_windowgrabber.cpp"