~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to ksystraycmd/ksystraycmd.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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::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
 
 
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
}