1
2#include <QTextStream>
3#include <QImage>
4#include <QRegExp>
5#include <QMouseEvent>
6
7#include <kdebug.h>
8#include <kapplication.h>
9#include <kglobal.h>
10#include <kicon.h>
11#include <klocale.h>
12#include <kmenu.h>
13#include <kprocess.h>
14#include <kwindowsystem.h>
15#include <kconfig.h>
16#include <ksystemtrayicon.h>
17#include <kconfiggroup.h>
18#include <kaboutdata.h>
19
20#include <netwm.h>
21
22#include "ksystraycmd.h"
23#include "ksystraycmd.moc"
24#include <QX11Info>
25
26
27KSysTrayCmd::KSysTrayCmd()
28 : KSystemTrayIcon( static_cast<QWidget*>(0) ),
29 isVisible(true), lazyStart( false ), noquit( false ),
30 quitOnHide( false ), onTop(false), ownIcon(false),
31 waitingForWindow( false ),
32 win(0), client(0), top(0), left(0)
33{
34 connect( KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(windowAdded(WId)) );
35
36 menu = new KMenu();
37 setContextMenu(menu);
38 connect(this, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(mousePressEvent(QSystemTrayIcon::ActivationReason)));
39 refresh();
40}
41
42KSysTrayCmd::~KSysTrayCmd()
43{
44 delete menu;
45 if( client )
46 {
47 if( client->state() == QProcess::Running )
48 {
49 client->terminate();
50 client->kill();
51 client->waitForFinished( 5000 );
52 }
53 delete client;
54 }
55}
56
57//
58// Main entry point to the class
59//
60
61bool KSysTrayCmd::start()
62{
63 // If we have no command we must catching an existing window
64 if ( command.isEmpty() ) {
65 if ( win ) {
66 setTargetWindow( win );
67 return true;
68 }
69
70 waitingForWindow = true;
71 checkExistingWindows();
72 if ( win ) {
73 // Window always on top
74 if (onTop) {
75 KWindowSystem::setState(win, NET::StaysOnTop);
76 }
77 return true;
78 }
79
80 errStr = i18n( "No window matching pattern '%1' and no command specified.\n" ,
81 window );
82 return false;
83 }
84
85 // Run the command and watch for its window
86 if ( !startClient() ) {
87 errStr = i18n( "KSysTrayCmd: K3ShellProcess cannot find a shell." );
88 clientExited();
89 return false;
90 }
91
92 return true;
93}
94
95//
96// Window related functions.
97//
98
99void KSysTrayCmd::showWindow()
100{
101 isVisible = true;
102 if ( !win )
103 return;
104 XMapWindow( QX11Info::display(), win );
105 // We move the window to the memorized position
106 XMoveWindow( QX11Info::display(), win, left, top);
107
108 // Window always on top
109 if (onTop)
110 {
111 KWindowSystem::setState(win, NET::StaysOnTop);
112 }
113
114 KWindowSystem::activateWindow( win );
115
116}
117
118void KSysTrayCmd::hideWindow()
119{
120 isVisible = false;
121 if ( !win )
122 return;
123 //We memorize the position of the window
124 left = KWindowSystem::windowInfo(win, NET::WMFrameExtents).frameGeometry().left();
125 top=KWindowSystem::windowInfo(win, NET::WMFrameExtents).frameGeometry().top();
126
127 XUnmapWindow( QX11Info::display(), win );
128}
129
130void KSysTrayCmd::setTargetWindow( WId w )
131{
132 disconnect( KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(windowAdded(WId)) );
133 connect( KWindowSystem::self(), SIGNAL(windowChanged(WId)), SLOT(windowChanged(WId)) );
134 win = w;
135// KWindowSystem::setSystemTrayWindowFor( winId(), win );
136 refresh();
137 show();
138
139 if ( isVisible )
140 KWindowSystem::activateWindow( win );
141 else
142 hideWindow();
143
144 // Always on top ?
145 if (onTop)
146 {
147 KWindowSystem::setState(win, NET::StaysOnTop);
148 }
149}
150
151//
152// Refresh the tray icon
153//
154
155void KSysTrayCmd::refresh()
156{
157// KWindowSystem::setSystemTrayWindowFor( winId(), win ? win : winId() );
158
159 if ( win ) {
160 if (ownIcon)
161 {
162 setIcon( KApplication::windowIcon() );
163 }
164 else
165 {
166 setIcon( KWindowSystem::icon( win, 22, 22, true ) );
167 }
168
169 if ( tooltip.isEmpty() )
170 this->setToolTip( KWindowSystem::windowInfo( win, NET::WMName ).name() );
171 }
172 else {
173 if ( !tooltip.isEmpty() )
174 this->setToolTip( tooltip );
175 else if ( !command.isEmpty() )
176 this->setToolTip( command );
177 else
178 this->setToolTip( window );
179
180 setIcon( KApplication::windowIcon() );
181 }
182}
183
184//
185// Client related functions.
186//
187
188bool KSysTrayCmd::startClient()
189{
190 kDebug() << "startClient()";
191 client = new KProcess();
192 client->setShellCommand( command );
193 //connect( KWindowSystem::self(), SIGNAL(windowAdded(WId)), this, SLOT(windowAdded(WId)) );
194 waitingForWindow = true;
195 connect( client, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(clientExited()) );
196
197 client->start();
198 return client->waitForStarted( -1 );
199}
200
201void KSysTrayCmd::clientExited()
202{
203 delete client;
204 client = 0;
205 win = 0;
206 waitingForWindow = false;
207
208 if ( lazyStart && noquit )
209 refresh();
210 else
211 qApp->quit();
212}
213
214void KSysTrayCmd::quitClient()
215{
216 if ( win ) {
217 // Before sending the close request we have to show the window
218 XMapWindow( QX11Info::display(), win );
219 NETRootInfo ri( QX11Info::display(), NET::CloseWindow );
220 ri.closeWindowRequest( win );
221 win=0;
222 noquit = false;
223
224 // We didn't give command, so we didn't open an application.
225 // That's why when the application is closed we aren't informed.
226 // So we quit now.
227
228 if ( command.isEmpty() ) {
229 qApp->quit();
230 }
231 }
232 else {
233 qApp->quit();
234 }
235}
236
237void KSysTrayCmd::quit()
238{
239 if ( !isVisible ) {
240 showWindow();
241 }
242 qApp->quit();
243}
244
245void KSysTrayCmd::execContextMenu( const QPoint &pos )
246{
247 menu->clear();
248 menu->addTitle( icon(), i18n( "KSysTrayCmd" ) );
249 QAction * hideShowId = menu->addAction( isVisible ? i18n( "&Hide" ) : i18n( "&Restore" ) );
250 QAction * undockId = menu->addAction( KIcon("dialog-close"), i18n( "&Undock" ) );
251 QAction * quitId = menu->addAction( KIcon("application-exit"), i18n( "&Quit" ) );
252
253 QAction * cmd = menu->exec( pos );
254
255 if ( cmd == quitId )
256 quitClient();
257 else if ( cmd == undockId )
258 quit();
259 else if ( cmd == hideShowId )
260 {
261 if ( lazyStart && ( !hasRunningClient() ) )
262 {
263 start();
264 isVisible=true;
265 }
266 else if ( quitOnHide && ( hasRunningClient() ) && isVisible )
267 {
268 NETRootInfo ri( QX11Info::display(), NET::CloseWindow );
269 ri.closeWindowRequest( win );
270 isVisible=false;
271 }
272 else
273 toggleWindow();
274 }
275}
276
277void KSysTrayCmd::checkExistingWindows()
278{
279 kDebug() << "checkExistingWindows()";
280 QList<WId>::ConstIterator it;
281 for ( it = KWindowSystem::windows().begin(); it != KWindowSystem::windows().end(); ++it ) {
282 windowAdded( *it );
283 if ( win )
284 break;
285 }
286}
287
288const int SUPPORTED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask
289 | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask
290 | NET::UtilityMask | NET::SplashMask;
291
292void KSysTrayCmd::windowAdded(WId w)
293{
294 if ( !waitingForWindow )
295 return;
296
297 KWindowInfo info = KWindowSystem::windowInfo( w, NET::WMWindowType | NET::WMName );
298 kDebug() << "windowAdded, id" << w << "pattern is " << window << " window is " << info.name();
299
300 // always ignore these window types
301 if( info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) == NET::TopMenu
302 || info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) == NET::Toolbar
303 || info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) == NET::Desktop )
304 return;
305
306 // If we're grabbing the first window we see
307 if( window.isEmpty() ) {
308 // accept only "normal" windows
309 if( info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) != NET::Unknown
310 && info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) != NET::Normal
311 && info.windowType( SUPPORTED_WINDOW_TYPES_MASK ) != NET::Dialog )
312 return;
313 }
314 else if ( QRegExp( window ).indexIn( info.name() ) == -1 ) {
315 return;
316 }
317
318 kDebug() << "windowAdded, setting target " << (int) w;
319 setTargetWindow( w );
320}
321
322void KSysTrayCmd::windowChanged( WId w )
323{
324 if ( w != win )
325 return;
326 refresh();
327}
328
329//
330// Tray icon event handlers
331//
332
333void KSysTrayCmd::mousePressEvent( QSystemTrayIcon::ActivationReason reason )
334{
335 if ( reason == QSystemTrayIcon::Context )
336 execContextMenu( QCursor::pos() );
337 else if ( lazyStart && ( !hasRunningClient() ) )
338 {
339 start();
340 isVisible=true;
341 }
342 else if ( quitOnHide && ( hasRunningClient() ) && isVisible )
343 {
344 NETRootInfo ri( QX11Info::display(), NET::CloseWindow );
345 ri.closeWindowRequest( win );
346 isVisible=false;
347 }
348 else if ( reason == QSystemTrayIcon::Trigger )
349 toggleWindow();
350}
351
352WId KSysTrayCmd::findRealWindow( WId w, int depth )
353{
354 if( depth > 5 )
355 return None;
356 static Atom wm_state = XInternAtom( QX11Info::display(), "WM_STATE", False );
357 Atom type;
358 int format;
359 unsigned long nitems, after;
360 unsigned char* prop;
361 if( XGetWindowProperty( QX11Info::display(), w, wm_state, 0, 0, False, AnyPropertyType,
362 &type, &format, &nitems, &after, &prop ) == Success ) {
363 if( prop != NULL )
364 XFree( prop );
365 if( type != None )
366 return w;
367 }
368 Window root, parent;
369 Window* children;
370 unsigned int nchildren;
371 Window ret = None;
372 if( XQueryTree( QX11Info::display(), w, &root, &parent, &children, &nchildren ) != 0 ) {
373 for( unsigned int i = 0;
374 i < nchildren && ret == None;
375 ++i )
376 ret = findRealWindow( children[ i ], depth + 1 );
377 if( children != NULL )
378 XFree( children );
379 }
380 return ret;
381}
382