1
/* This file is part of the KDE project
2
Copyright (C) 2001 Lubos Lunak <l.lunak@kde.org>
3
Copyright (C) 2010 Martin Gräßlin <kde@martin-graesslin.com>
5
This library is free software; you can redistribute it and/or
6
modify it under the terms of the GNU Library 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.
10
This library 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.
15
You should have received a copy of the GNU Library General Public License
16
along with this library; see the file COPYING.LIB. If not, write to
17
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18
Boston, MA 02110-1301, USA.
21
#include "startupid.h"
23
#include <config-X11.h>
25
#include "klaunchsettings.h"
27
#include <kiconloader.h>
28
#include <kmanagerselection.h>
30
#include <kapplication.h>
39
#include <X11/Xutil.h>
40
#include <X11/Xatom.h>
41
#include <X11/extensions/shape.h>
43
#define KDE_STARTUP_ICON QLatin1String( "kmenu" )
46
#include <X11/Xcursor/Xcursor.h>
49
enum kde_startup_status_enum { StartupPre, StartupIn, StartupDone };
50
static kde_startup_status_enum kde_startup_status = StartupPre;
51
static Atom kde_splash_progress;
53
StartupId::StartupId( QWidget* parent, const char* name )
55
startup_info( KStartupInfo::CleanOnCantDetect ),
56
startup_window( None ),
59
selection_watcher( new KSelectionWatcher( "_KDE_STARTUP_FEEDBACK", -1, this ))
61
setObjectName( QLatin1String( name ) );
62
hide(); // is QWidget only because of x11Event()
63
if( kde_startup_status == StartupPre )
65
kde_splash_progress = XInternAtom( QX11Info::display(), "_KDE_SPLASH_PROGRESS", False );
66
XWindowAttributes attrs;
67
XGetWindowAttributes( QX11Info::display(), QX11Info::appRootWindow(), &attrs);
68
XSelectInput( QX11Info::display(), QX11Info::appRootWindow(), attrs.your_event_mask | SubstructureNotifyMask);
69
kapp->installX11EventFilter( this );
71
update_timer.setSingleShot( true );
72
connect( &update_timer, SIGNAL( timeout()), SLOT( update_startupid()));
73
connect( &startup_info,
74
SIGNAL( gotNewStartup( const KStartupInfoId&, const KStartupInfoData& )),
75
SLOT( gotNewStartup( const KStartupInfoId&, const KStartupInfoData& )));
76
connect( &startup_info,
77
SIGNAL( gotStartupChange( const KStartupInfoId&, const KStartupInfoData& )),
78
SLOT( gotStartupChange( const KStartupInfoId&, const KStartupInfoData& )));
79
connect( &startup_info,
80
SIGNAL( gotRemoveStartup( const KStartupInfoId&, const KStartupInfoData& )),
81
SLOT( gotRemoveStartup( const KStartupInfoId& )));
82
connect( selection_watcher, SIGNAL(newOwner(Window)), SLOT(newOwner()));
83
connect( selection_watcher, SIGNAL(lostOwner()), SLOT(lostOwner()));
84
active_selection = ( selection_watcher->owner() != None );
87
StartupId::~StartupId()
92
void StartupId::configure()
94
startup_info.setTimeout( KLaunchSettings::timeout());
95
blinking = KLaunchSettings::blinking();
96
bouncing = KLaunchSettings::bouncing();
99
void StartupId::gotNewStartup( const KStartupInfoId& id_P, const KStartupInfoData& data_P )
101
if( active_selection )
103
QString icon = data_P.findIcon();
104
current_startup = id_P;
105
startups[ id_P ] = icon;
106
start_startupid( icon );
109
void StartupId::gotStartupChange( const KStartupInfoId& id_P, const KStartupInfoData& data_P )
111
if( active_selection )
113
if( current_startup == id_P )
115
QString icon = data_P.findIcon();
116
if( !icon.isEmpty() && icon != startups[ current_startup ] )
118
startups[ id_P ] = icon;
119
start_startupid( icon );
124
void StartupId::gotRemoveStartup( const KStartupInfoId& id_P )
126
if( active_selection )
128
startups.remove( id_P );
129
if( startups.count() == 0 )
131
current_startup = KStartupInfoId(); // null
132
if( kde_startup_status == StartupIn )
133
start_startupid( KDE_STARTUP_ICON );
138
current_startup = startups.begin().key();
139
start_startupid( startups[ current_startup ] );
142
bool StartupId::x11Event( XEvent* e )
144
if( e->type == ClientMessage && e->xclient.window == QX11Info::appRootWindow()
145
&& e->xclient.message_type == kde_splash_progress )
147
const char* s = e->xclient.data.b;
148
if( strcmp( s, "desktop" ) == 0 && kde_startup_status == StartupPre )
150
kde_startup_status = StartupIn;
151
if( startups.count() == 0 )
152
start_startupid( KDE_STARTUP_ICON );
153
// 60(?) sec timeout - shouldn't be hopefully needed anyway, ksmserver should have it too
154
QTimer::singleShot( 60000, this, SLOT( finishKDEStartup()));
156
else if( strcmp( s, "ready" ) == 0 && kde_startup_status < StartupDone )
157
QTimer::singleShot( 2000, this, SLOT( finishKDEStartup()));
162
void StartupId::finishKDEStartup()
164
kde_startup_status = StartupDone;
165
kapp->removeX11EventFilter( this );
166
if( startups.count() == 0 )
170
void StartupId::stop_startupid()
172
if( startup_window != None )
173
XDestroyWindow( QX11Info::display(), startup_window );
174
startup_window = None;
177
i < NUM_BLINKING_PIXMAPS;
179
pixmaps[ i ] = QPixmap(); // null
183
static QPixmap scalePixmap( const QPixmap& pm, int w, int h )
185
QImage scaled = pm.toImage().scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
186
if (scaled.format() != QImage::Format_ARGB32_Premultiplied && scaled.format() != QImage::Format_ARGB32)
187
scaled = scaled.convertToFormat(QImage::Format_ARGB32_Premultiplied);
189
QImage result(20, 20, QImage::Format_ARGB32_Premultiplied);
191
p.setCompositionMode(QPainter::CompositionMode_Source);
192
p.fillRect(result.rect(), Qt::transparent);
193
p.drawImage((20 - w) / 2, (20 - h) / 2, scaled, 0, 0, w, h);
194
return QPixmap::fromImage(result);
197
// Transparent images are converted to 32bpp pixmaps, but
198
// setting those as window background needs ARGB visual
199
// and compositing - convert to 24bpp, at least for now.
200
static QPixmap make24bpp( const QPixmap& p )
202
QPixmap ret( p.size());
204
pt.drawPixmap( 0, 0, p );
206
ret.setMask( p.mask());
210
void StartupId::start_startupid( const QString& icon_P )
213
const QColor startup_colors[ StartupId::NUM_BLINKING_PIXMAPS ]
214
= { Qt::black, Qt::darkGray, Qt::lightGray, Qt::white, Qt::white };
217
QPixmap icon_pixmap = KIconLoader::global()->loadIcon( icon_P, KIconLoader::Small, 0,
218
KIconLoader::DefaultState, QStringList(), 0, true ); // return null pixmap if not found
219
if( icon_pixmap.isNull())
220
icon_pixmap = SmallIcon( QLatin1String( "system-run" ) );
221
if( startup_window == None )
223
XSetWindowAttributes attrs;
224
attrs.override_redirect = True;
225
attrs.save_under = True; // useful saveunder if possible to avoid redrawing
226
attrs.colormap = QX11Info::appColormap();
227
attrs.background_pixel = WhitePixel( QX11Info::display(), QX11Info::appScreen());
228
attrs.border_pixel = BlackPixel( QX11Info::display(), QX11Info::appScreen());
229
startup_window = XCreateWindow( QX11Info::display(), DefaultRootWindow( QX11Info::display()),
230
0, 0, 1, 1, 0, QX11Info::appDepth(), InputOutput, static_cast< Visual* >( QX11Info::appVisual()),
231
CWOverrideRedirect | CWSaveUnder | CWColormap | CWBackPixel | CWBorderPixel, &attrs );
232
XClassHint class_hint;
233
QByteArray cls = qAppName().toLatin1();
234
class_hint.res_name = cls.data();
235
class_hint.res_class = const_cast< char* >( QX11Info::appClass());
236
XSetWMProperties( QX11Info::display(), startup_window, NULL, NULL, NULL, 0, NULL, NULL, &class_hint );
237
XChangeProperty( QX11Info::display(), winId(),
238
XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace,
239
(unsigned char *)"startupfeedback", strlen( "startupfeedback" ));
241
XResizeWindow( QX11Info::display(), startup_window, icon_pixmap.width(), icon_pixmap.height());
244
XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, None, ShapeSet );
245
int window_w = icon_pixmap.width();
246
int window_h = icon_pixmap.height();
248
i < NUM_BLINKING_PIXMAPS;
251
pixmaps[ i ] = QPixmap( window_w, window_h );
252
pixmaps[ i ].fill( startup_colors[ i ] );
253
QPainter p( &pixmaps[ i ] );
254
p.drawPixmap( 0, 0, icon_pixmap );
261
XResizeWindow( QX11Info::display(), startup_window, 20, 20 );
262
pixmaps[ 0 ] = make24bpp( scalePixmap( icon_pixmap, 16, 16 ));
263
pixmaps[ 1 ] = make24bpp( scalePixmap( icon_pixmap, 14, 18 ));
264
pixmaps[ 2 ] = make24bpp( scalePixmap( icon_pixmap, 12, 20 ));
265
pixmaps[ 3 ] = make24bpp( scalePixmap( icon_pixmap, 18, 14 ));
266
pixmaps[ 4 ] = make24bpp( scalePixmap( icon_pixmap, 20, 12 ));
271
icon_pixmap = make24bpp( icon_pixmap );
272
if( !icon_pixmap.mask().isNull() ) // set mask
273
XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0,
274
icon_pixmap.mask().handle(), ShapeSet );
276
XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, None, ShapeSet );
277
XSetWindowBackgroundPixmap( QX11Info::display(), startup_window, icon_pixmap.handle());
278
XClearWindow( QX11Info::display(), startup_window );
285
const int X_DIFF = 15;
286
const int Y_DIFF = 15;
287
const int color_to_pixmap[] = { 0, 1, 2, 3, 2, 1 };
288
const int frame_to_yoffset[] =
290
-5, -1, 2, 5, 8, 10, 12, 13, 15, 15, 15, 15, 14, 12, 10, 8, 5, 2, -1, -5
292
const int frame_to_pixmap[] =
294
0, 0, 0, 1, 2, 2, 1, 0, 3, 4, 4, 3, 0, 1, 2, 2, 1, 0, 0, 0
298
void StartupId::update_startupid()
303
XSetWindowBackgroundPixmap( QX11Info::display(), startup_window,
304
pixmaps[ color_to_pixmap[ color_index ]].handle());
305
XClearWindow( QX11Info::display(), startup_window );
306
if( ++color_index >= ( sizeof( color_to_pixmap ) / sizeof( color_to_pixmap[ 0 ] )))
311
yoffset = frame_to_yoffset[ frame ];
312
QPixmap pixmap = pixmaps[ frame_to_pixmap[ frame ] ];
313
XSetWindowBackgroundPixmap( QX11Info::display(), startup_window, pixmap.handle());
314
XClearWindow( QX11Info::display(), startup_window );
315
if ( !pixmap.mask().isNull() ) // set mask
316
XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0,
317
pixmap.mask().handle(), ShapeSet );
319
XShapeCombineMask( QX11Info::display(), startup_window, ShapeBounding, 0, 0, None, ShapeSet );
320
if ( ++frame >= ( sizeof( frame_to_yoffset ) / sizeof( frame_to_yoffset[ 0 ] ) ) )
323
Window dummy1, dummy2;
327
if( !XQueryPointer( QX11Info::display(), QX11Info::appRootWindow(), &dummy1, &dummy2, &x, &y, &dummy3, &dummy4, &dummy5 ))
329
XUnmapWindow( QX11Info::display(), startup_window );
330
update_timer.start( 100 );
333
QPoint c_pos( x, y );
336
cursor_size = XcursorGetDefaultSize( QX11Info::display());
339
if( cursor_size <= 16 )
341
else if( cursor_size <= 32 )
343
else if( cursor_size <= 48 )
348
XMoveWindow( QX11Info::display(), startup_window, c_pos.x() + X_DIFF, c_pos.y() + Y_DIFF + yoffset );
349
XMapWindow( QX11Info::display(), startup_window );
350
XRaiseWindow( QX11Info::display(), startup_window );
351
update_timer.start( bouncing ? 30 : 100 );
352
QApplication::flush();
355
void StartupId::newOwner()
357
active_selection = true;
360
void StartupId::lostOwner()
362
active_selection = false;
365
#include "startupid.moc"