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 | |
27 | KSysTrayCmd::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 | |
42 | KSysTrayCmd::~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 | |
61 | bool 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 | |
99 | void 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 | |
118 | void 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 | |
130 | void 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 | |
155 | void 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 | |
188 | bool 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 | |
201 | void 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 | |
214 | void 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 | |
237 | void KSysTrayCmd::quit() |
238 | { |
239 | if ( !isVisible ) { |
240 | showWindow(); |
241 | } |
242 | qApp->quit(); |
243 | } |
244 | |
245 | void KSysTrayCmd::( 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 | |
277 | void 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 | |
288 | const 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 | |
292 | void 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 | |
322 | void KSysTrayCmd::windowChanged( WId w ) |
323 | { |
324 | if ( w != win ) |
325 | return; |
326 | refresh(); |
327 | } |
328 | |
329 | // |
330 | // Tray icon event handlers |
331 | // |
332 | |
333 | void 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 | |
352 | WId 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 | |