1 | /*
|
---|
2 | * trayicon_x11.cpp - X11 trayicon (for use with KDE and GNOME)
|
---|
3 | * Copyright (C) 2003 Justin Karneges
|
---|
4 | * GNOME2 Notification Area support: Tomasz Sterna
|
---|
5 | *
|
---|
6 | * This library is free software; you can redistribute it and/or
|
---|
7 | * modify it under the terms of the GNU Lesser General Public
|
---|
8 | * License as published by the Free Software Foundation; either
|
---|
9 | * version 2.1 of the License, or (at your option) any later version.
|
---|
10 | *
|
---|
11 | * This library is distributed in the hope that it will be useful,
|
---|
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
14 | * Lesser General Public License for more details.
|
---|
15 | *
|
---|
16 | * You should have received a copy of the GNU Lesser General Public
|
---|
17 | * License along with this library; if not, write to the Free Software
|
---|
18 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
---|
19 | *
|
---|
20 | */
|
---|
21 |
|
---|
22 | #include "trayicon.h"
|
---|
23 |
|
---|
24 | #include<qapplication.h>
|
---|
25 | #include<qimage.h>
|
---|
26 | #include<qpixmap.h>
|
---|
27 | #include<qtooltip.h>
|
---|
28 | #include<qpainter.h>
|
---|
29 |
|
---|
30 | #include<X11/Xlib.h>
|
---|
31 | #include<X11/Xutil.h>
|
---|
32 | #include<X11/Xatom.h>
|
---|
33 |
|
---|
34 | //#if QT_VERSION < 0x030200
|
---|
35 | extern Time qt_x_time;
|
---|
36 | //#endif
|
---|
37 |
|
---|
38 | //----------------------------------------------------------------------------
|
---|
39 | // common stuff
|
---|
40 | //----------------------------------------------------------------------------
|
---|
41 |
|
---|
42 | // for Gnome2 Notification Area
|
---|
43 | static XErrorHandler old_handler = 0;
|
---|
44 | static int dock_xerror = 0;
|
---|
45 | extern "C" int dock_xerrhandler(Display* dpy, XErrorEvent* err)
|
---|
46 | {
|
---|
47 | dock_xerror = err->error_code;
|
---|
48 | return old_handler(dpy, err);
|
---|
49 | }
|
---|
50 |
|
---|
51 | static void trap_errors()
|
---|
52 | {
|
---|
53 | dock_xerror = 0;
|
---|
54 | old_handler = XSetErrorHandler(dock_xerrhandler);
|
---|
55 | }
|
---|
56 |
|
---|
57 | static bool untrap_errors()
|
---|
58 | {
|
---|
59 | XSetErrorHandler(old_handler);
|
---|
60 | return (dock_xerror == 0);
|
---|
61 | }
|
---|
62 |
|
---|
63 | static bool send_message(
|
---|
64 | Display* dpy, /* display */
|
---|
65 | Window w, /* sender (tray icon window) */
|
---|
66 | long message, /* message opcode */
|
---|
67 | long data1, /* message data 1 */
|
---|
68 | long data2, /* message data 2 */
|
---|
69 | long data3 /* message data 3 */
|
---|
70 | ) {
|
---|
71 | XEvent ev;
|
---|
72 |
|
---|
73 | memset(&ev, 0, sizeof(ev));
|
---|
74 | ev.xclient.type = ClientMessage;
|
---|
75 | ev.xclient.window = w;
|
---|
76 | ev.xclient.message_type = XInternAtom (dpy, "_NET_SYSTEM_TRAY_OPCODE", False );
|
---|
77 | ev.xclient.format = 32;
|
---|
78 | ev.xclient.data.l[0] = CurrentTime;
|
---|
79 | ev.xclient.data.l[1] = message;
|
---|
80 | ev.xclient.data.l[2] = data1;
|
---|
81 | ev.xclient.data.l[3] = data2;
|
---|
82 | ev.xclient.data.l[4] = data3;
|
---|
83 |
|
---|
84 | trap_errors();
|
---|
85 | XSendEvent(dpy, w, False, NoEventMask, &ev);
|
---|
86 | XSync(dpy, False);
|
---|
87 | return untrap_errors();
|
---|
88 | }
|
---|
89 |
|
---|
90 | #define SYSTEM_TRAY_REQUEST_DOCK 0
|
---|
91 | #define SYSTEM_TRAY_BEGIN_MESSAGE 1
|
---|
92 | #define SYSTEM_TRAY_CANCEL_MESSAGE 2
|
---|
93 |
|
---|
94 | //----------------------------------------------------------------------------
|
---|
95 | // TrayIcon::TrayIconPrivate
|
---|
96 | //----------------------------------------------------------------------------
|
---|
97 |
|
---|
98 | class TrayIcon::TrayIconPrivate : public QWidget
|
---|
99 | {
|
---|
100 | public:
|
---|
101 | TrayIconPrivate(TrayIcon *object, int size);
|
---|
102 | ~TrayIconPrivate() { }
|
---|
103 |
|
---|
104 | virtual void initWM(WId icon);
|
---|
105 |
|
---|
106 | virtual void setPixmap(const QPixmap &pm);
|
---|
107 |
|
---|
108 | virtual void paintEvent(QPaintEvent *);
|
---|
109 | virtual void enterEvent(QEvent *);
|
---|
110 | virtual void mouseMoveEvent(QMouseEvent *e);
|
---|
111 | virtual void mousePressEvent(QMouseEvent *e);
|
---|
112 | virtual void mouseReleaseEvent(QMouseEvent *e);
|
---|
113 | virtual void mouseDoubleClickEvent(QMouseEvent *e);
|
---|
114 | virtual void closeEvent(QCloseEvent *e);
|
---|
115 |
|
---|
116 | private:
|
---|
117 | TrayIcon *iconObject;
|
---|
118 | QPixmap pix;
|
---|
119 | int size;
|
---|
120 | };
|
---|
121 |
|
---|
122 | TrayIcon::TrayIconPrivate::TrayIconPrivate(TrayIcon *object, int _size)
|
---|
123 | : QWidget(0, "psidock", WRepaintNoErase)
|
---|
124 | {
|
---|
125 | iconObject = object;
|
---|
126 | size = _size;
|
---|
127 |
|
---|
128 | setFocusPolicy(NoFocus);
|
---|
129 | setBackgroundMode(X11ParentRelative);
|
---|
130 |
|
---|
131 | setMinimumSize(size, size);
|
---|
132 | setMaximumSize(size, size);
|
---|
133 | }
|
---|
134 |
|
---|
135 | // This base stuff is required by both FreeDesktop specification and WindowMaker
|
---|
136 | void TrayIcon::TrayIconPrivate::initWM(WId icon)
|
---|
137 | {
|
---|
138 | Display *dsp = x11Display();
|
---|
139 | WId leader = winId();
|
---|
140 |
|
---|
141 | // set the class hint
|
---|
142 | XClassHint classhint;
|
---|
143 | classhint.res_name = (char*)"psidock";
|
---|
144 | classhint.res_class = (char*)"Psi";
|
---|
145 | XSetClassHint(dsp, leader, &classhint);
|
---|
146 |
|
---|
147 | // set the Window Manager hints
|
---|
148 | XWMHints *hints;
|
---|
149 | hints = XGetWMHints(dsp, leader); // init hints
|
---|
150 | hints->flags = WindowGroupHint | IconWindowHint | StateHint; // set the window group hint
|
---|
151 | hints->window_group = leader; // set the window hint
|
---|
152 | hints->initial_state = WithdrawnState; // initial state
|
---|
153 | hints->icon_window = icon; // in WM, this should be winId() of separate widget
|
---|
154 | hints->icon_x = 0;
|
---|
155 | hints->icon_y = 0;
|
---|
156 | XSetWMHints(dsp, leader, hints); // set the window hints for WM to use.
|
---|
157 | XFree( hints );
|
---|
158 | }
|
---|
159 |
|
---|
160 | void TrayIcon::TrayIconPrivate::setPixmap(const QPixmap &pm)
|
---|
161 | {
|
---|
162 | pix = pm;
|
---|
163 | setIcon(pix);
|
---|
164 | repaint();
|
---|
165 | }
|
---|
166 |
|
---|
167 | void TrayIcon::TrayIconPrivate::paintEvent(QPaintEvent *)
|
---|
168 | {
|
---|
169 | QPainter p(this);
|
---|
170 | p.drawPixmap((width() - pix.width())/2, (height() - pix.height())/2, pix);
|
---|
171 | }
|
---|
172 |
|
---|
173 | void TrayIcon::TrayIconPrivate::enterEvent(QEvent *e)
|
---|
174 | {
|
---|
175 | // Taken from KSystemTray..
|
---|
176 | //#if QT_VERSION < 0x030200
|
---|
177 | //if ( !qApp->focusWidget() ) {
|
---|
178 | XEvent ev;
|
---|
179 | memset(&ev, 0, sizeof(ev));
|
---|
180 | ev.xfocus.display = qt_xdisplay();
|
---|
181 | ev.xfocus.type = FocusIn;
|
---|
182 | ev.xfocus.window = winId();
|
---|
183 | ev.xfocus.mode = NotifyNormal;
|
---|
184 | ev.xfocus.detail = NotifyAncestor;
|
---|
185 | Time time = qt_x_time;
|
---|
186 | qt_x_time = 1;
|
---|
187 | qApp->x11ProcessEvent( &ev );
|
---|
188 | qt_x_time = time;
|
---|
189 | //}
|
---|
190 | //#endif
|
---|
191 | QWidget::enterEvent(e);
|
---|
192 | }
|
---|
193 |
|
---|
194 | void TrayIcon::TrayIconPrivate::mouseMoveEvent(QMouseEvent *e)
|
---|
195 | {
|
---|
196 | QApplication::sendEvent(iconObject, e);
|
---|
197 | }
|
---|
198 |
|
---|
199 | void TrayIcon::TrayIconPrivate::mousePressEvent(QMouseEvent *e)
|
---|
200 | {
|
---|
201 | QApplication::sendEvent(iconObject, e);
|
---|
202 | }
|
---|
203 |
|
---|
204 | void TrayIcon::TrayIconPrivate::mouseReleaseEvent(QMouseEvent *e)
|
---|
205 | {
|
---|
206 | QApplication::sendEvent(iconObject, e);
|
---|
207 | }
|
---|
208 |
|
---|
209 | void TrayIcon::TrayIconPrivate::mouseDoubleClickEvent(QMouseEvent *e)
|
---|
210 | {
|
---|
211 | QApplication::sendEvent(iconObject, e);
|
---|
212 | }
|
---|
213 |
|
---|
214 | void TrayIcon::TrayIconPrivate::closeEvent(QCloseEvent *e)
|
---|
215 | {
|
---|
216 | iconObject->gotCloseEvent();
|
---|
217 | e->accept();
|
---|
218 | }
|
---|
219 |
|
---|
220 | //----------------------------------------------------------------------------
|
---|
221 | // TrayIconFreeDesktop
|
---|
222 | //----------------------------------------------------------------------------
|
---|
223 |
|
---|
224 | class TrayIconFreeDesktop : public TrayIcon::TrayIconPrivate
|
---|
225 | {
|
---|
226 | public:
|
---|
227 | TrayIconFreeDesktop(TrayIcon *object, const QPixmap &pm);
|
---|
228 | protected:
|
---|
229 | virtual bool x11Event(XEvent*);
|
---|
230 | };
|
---|
231 |
|
---|
232 | TrayIconFreeDesktop::TrayIconFreeDesktop(TrayIcon *object, const QPixmap &pm)
|
---|
233 | : TrayIconPrivate(object, 22)
|
---|
234 | {
|
---|
235 | initWM( winId() );
|
---|
236 |
|
---|
237 | // initialize NetWM
|
---|
238 | Display *dsp = x11Display();
|
---|
239 |
|
---|
240 | // dock the widget (adapted from SIM-ICQ)
|
---|
241 | Screen *screen = XDefaultScreenOfDisplay(dsp); // get the screen
|
---|
242 | int screen_id = XScreenNumberOfScreen(screen); // and it's number
|
---|
243 |
|
---|
244 | // tell X that we want to see ClientMessage and Deleted events, which
|
---|
245 | // are picked up by QApplication::x11EventFilter
|
---|
246 | Window root_window = QApplication::desktop()->winId();
|
---|
247 | XWindowAttributes attr;
|
---|
248 |
|
---|
249 | XGetWindowAttributes(dsp, root_window, &attr);
|
---|
250 | XSelectInput(dsp, root_window, attr.your_event_mask | StructureNotifyMask);
|
---|
251 |
|
---|
252 | char buf[32];
|
---|
253 | snprintf(buf, sizeof(buf), "_NET_SYSTEM_TRAY_S%d", screen_id);
|
---|
254 | Atom selection_atom = XInternAtom(dsp, buf, false);
|
---|
255 | XGrabServer(dsp);
|
---|
256 | Window manager_window = XGetSelectionOwner(dsp, selection_atom);
|
---|
257 | if ( manager_window != None )
|
---|
258 | XSelectInput(dsp, manager_window, StructureNotifyMask);
|
---|
259 | XUngrabServer(dsp);
|
---|
260 | XFlush(dsp);
|
---|
261 |
|
---|
262 | if ( manager_window != None )
|
---|
263 | send_message(dsp, manager_window, SYSTEM_TRAY_REQUEST_DOCK, winId(), 0, 0);
|
---|
264 | else
|
---|
265 | {
|
---|
266 | object->hide();
|
---|
267 | return;
|
---|
268 | }
|
---|
269 |
|
---|
270 | // some KDE mumbo-jumbo... why is it there? anybody?
|
---|
271 | Atom kwm_dockwindow_atom = XInternAtom(dsp, "KWM_DOCKWINDOW", false);
|
---|
272 | Atom kde_net_system_tray_window_for_atom = XInternAtom(dsp, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", false);
|
---|
273 |
|
---|
274 | long data = 0;
|
---|
275 | XChangeProperty(dsp, winId(), kwm_dockwindow_atom, kwm_dockwindow_atom, 32, PropModeReplace, (uchar*)&data, 1);
|
---|
276 | XChangeProperty(dsp, winId(), kde_net_system_tray_window_for_atom, XA_WINDOW, 32, PropModeReplace, (uchar*)&data, 1);
|
---|
277 |
|
---|
278 | setPixmap(pm);
|
---|
279 | }
|
---|
280 |
|
---|
281 | bool TrayIconFreeDesktop::x11Event(XEvent *ev)
|
---|
282 | {
|
---|
283 | switch(ev->type)
|
---|
284 | {
|
---|
285 | case ReparentNotify:
|
---|
286 | show();
|
---|
287 |
|
---|
288 | }
|
---|
289 | return false;
|
---|
290 | }
|
---|
291 |
|
---|
292 | //----------------------------------------------------------------------------
|
---|
293 | // TrayIconWindowMaker
|
---|
294 | //----------------------------------------------------------------------------
|
---|
295 |
|
---|
296 | class TrayIconWharf : public TrayIcon::TrayIconPrivate
|
---|
297 | {
|
---|
298 | public:
|
---|
299 | TrayIconWharf(TrayIcon *object, const QPixmap &pm)
|
---|
300 | : TrayIconPrivate(object, 44)
|
---|
301 | {
|
---|
302 | // set the class hint
|
---|
303 | XClassHint classhint;
|
---|
304 | classhint.res_name = (char*)"psidock-wharf";
|
---|
305 | classhint.res_class = (char*)"Psi";
|
---|
306 | XSetClassHint(x11Display(), winId(), &classhint);
|
---|
307 |
|
---|
308 | setPixmap(pm);
|
---|
309 | }
|
---|
310 |
|
---|
311 | void setPixmap(const QPixmap &_pm)
|
---|
312 | {
|
---|
313 | QPixmap pm;
|
---|
314 | QImage i = _pm.convertToImage();
|
---|
315 | i = i.scale(i.width() * 2, i.height() * 2);
|
---|
316 | pm.convertFromImage(i);
|
---|
317 |
|
---|
318 | TrayIconPrivate::setPixmap(pm);
|
---|
319 |
|
---|
320 | // thanks to Robert Spier for this:
|
---|
321 | // for some reason the repaint() isn't being honored, or isn't for
|
---|
322 | // the icon. So force one on the widget behind the icon
|
---|
323 | erase();
|
---|
324 | QPaintEvent pe( rect() );
|
---|
325 | paintEvent(&pe);
|
---|
326 | }
|
---|
327 | };
|
---|
328 |
|
---|
329 | class TrayIconWindowMaker : public TrayIcon::TrayIconPrivate
|
---|
330 | {
|
---|
331 | public:
|
---|
332 | TrayIconWindowMaker(TrayIcon *object, const QPixmap &pm);
|
---|
333 | ~TrayIconWindowMaker();
|
---|
334 |
|
---|
335 | void setPixmap(const QPixmap &pm);
|
---|
336 |
|
---|
337 | private:
|
---|
338 | TrayIconWharf *wharf;
|
---|
339 | };
|
---|
340 |
|
---|
341 | TrayIconWindowMaker::TrayIconWindowMaker(TrayIcon *object, const QPixmap &pm)
|
---|
342 | : TrayIconPrivate(object, 32)
|
---|
343 | {
|
---|
344 | wharf = new TrayIconWharf(object, pm);
|
---|
345 |
|
---|
346 | initWM( wharf->winId() );
|
---|
347 | }
|
---|
348 |
|
---|
349 | TrayIconWindowMaker::~TrayIconWindowMaker()
|
---|
350 | {
|
---|
351 | delete wharf;
|
---|
352 | }
|
---|
353 |
|
---|
354 | void TrayIconWindowMaker::setPixmap(const QPixmap &pm)
|
---|
355 | {
|
---|
356 | wharf->setPixmap(pm);
|
---|
357 | }
|
---|
358 |
|
---|
359 | //----------------------------------------------------------------------------
|
---|
360 | // TrayIcon
|
---|
361 | //----------------------------------------------------------------------------
|
---|
362 |
|
---|
363 | void TrayIcon::sysInstall()
|
---|
364 | {
|
---|
365 | if ( d )
|
---|
366 | return;
|
---|
367 |
|
---|
368 | if ( v_isWMDock )
|
---|
369 | d = (TrayIconPrivate *)(new TrayIconWindowMaker(this, pm));
|
---|
370 | else
|
---|
371 | d = (TrayIconPrivate *)(new TrayIconFreeDesktop(this, pm));
|
---|
372 |
|
---|
373 | sysUpdateToolTip();
|
---|
374 |
|
---|
375 | if ( v_isWMDock )
|
---|
376 | d->show();
|
---|
377 | }
|
---|
378 |
|
---|
379 | void TrayIcon::sysRemove()
|
---|
380 | {
|
---|
381 | if ( !d )
|
---|
382 | return;
|
---|
383 |
|
---|
384 | delete d;
|
---|
385 | d = 0;
|
---|
386 | }
|
---|
387 |
|
---|
388 | void TrayIcon::sysUpdateIcon()
|
---|
389 | {
|
---|
390 | if ( !d )
|
---|
391 | return;
|
---|
392 |
|
---|
393 | QPixmap pix = pm;
|
---|
394 | d->setPixmap(pix);
|
---|
395 | }
|
---|
396 |
|
---|
397 | void TrayIcon::sysUpdateToolTip()
|
---|
398 | {
|
---|
399 | if ( !d )
|
---|
400 | return;
|
---|
401 |
|
---|
402 | if ( tip.isEmpty() )
|
---|
403 | QToolTip::remove(d);
|
---|
404 | else
|
---|
405 | QToolTip::add(d, tip);
|
---|
406 | }
|
---|