1 #include "config.h"
2 #include "yxtray.h"
3 #include "ytimer.h"
4 #include "yprefs.h"
5 #include "default.h"
6 #include "wmoption.h"
7 #include <X11/Xutil.h>
8 #include <X11/Xproto.h>
9 #include <X11/extensions/Xcomposite.h>
10 
11 extern YColorName taskBarBg;
12 
13 static const long MaxTrayMessageSize = 256;
14 static const long MaxTrayNumMessages = 10;
15 static const long MaxTrayTooltipSize = 1024;
16 static const long MaxTrayTooltipTime = 10*1000;
17 
18 class TrayMessage {
19 public:
TrayMessage(Window win,long msec,long len,long ident)20     TrayMessage(Window win, long msec, long len, long ident) :
21         window(win),
22         milli(clamp(msec, 0L, MaxTrayTooltipTime)),
23         length(clamp(len, 0L, MaxTrayMessageSize)),
24         ident(ident),
25         offset(0L),
26         finish(monotime() + millitime(milli))
27     {
28     }
29 
30     const Window window;
31     const long milli;
32     const long length;
33     const long ident;
34     csmart bytes;
35     long offset;
36     const timeval finish;
37 
38     bool append(const char* data, long size);
39 };
40 
append(const char * data,long size)41 bool TrayMessage::append(const char* data, long size) {
42     MSG(("TrayMessage::append"));
43     if (offset < length) {
44         if (bytes == nullptr) {
45             bytes = new char[1 + length];
46         }
47         long extra = min(length - offset, size);
48         memcpy(bytes + offset, data, extra);
49         bytes[offset + extra] = 0;
50         offset += extra;
51         return true;
52     }
53     return false;
54 }
55 
windowDestroyed(Window win)56 static bool windowDestroyed(Window win) {
57     XWindowAttributes attr;
58     return None == XGetWindowAttributes(xapp->display(), win, &attr);
59 }
60 
getOrder(mstring title)61 static int getOrder(mstring title) {
62     WindowOption opt(title);
63     if (hintOptions)
64         hintOptions->mergeWindowOption(opt, title, true);
65     if (defOptions)
66         defOptions->mergeWindowOption(opt, title, false);
67     return opt.order;
68 }
69 
70 struct DockRequest {
71     Window window;
72     lazy<YTimer> timer;
73     mstring title;
DockRequestDockRequest74     DockRequest(Window w, YTimer* t, mstring s):
75         window(w), timer(t), title(s)
76     { }
77 };
78 
79 class YXTrayProxy: public YWindow, private YTimerListener {
80 public:
81     YXTrayProxy(const YAtom& atom, YXTray *tray);
82     virtual ~YXTrayProxy();
83 
84     virtual void handleClientMessage(const XClientMessageEvent &message);
85 private:
86     YAtom _NET_SYSTEM_TRAY_S0;
87     YXTray *fTray;
88     lazy<YTimer> fUpdateTimer;
89     YObjectArray<DockRequest> fDockRequests;
90     typedef YObjectArray<DockRequest>::IterType DockIter;
91     mstring toolTip;
92     unsigned char error_code;
93     unsigned char request_code;
94     static YXTrayProxy* singleton;
95 
96     bool requestDock(Window win);
97     bool enableBackingStore(Window win);
98     void handleError(XErrorEvent *xerr);
99     mstring fetchTitle(Window win);
100     static int dockError(Display *disp, XErrorEvent *xerr);
101 
102     typedef YObjectArray<TrayMessage> MessageListType;
103     typedef MessageListType::IterType IterType;
104     MessageListType messages;
105 
106     void beginMessage(Window win, long msec, long size, long ident);
107     void cancelMessage(Window win, long ident);
108     void messageData(Window win, const char* bytes, long maxlen);
109     void expireMessages();
110 
isExternal() const111     bool isExternal() const {
112         static const char s0[] = "_NET_SYSTEM_TRAY_S";
113         return 0 == strncmp(_NET_SYSTEM_TRAY_S0.str(), s0, sizeof s0 - 1);
114     }
trace() const115     bool trace() const {
116         return fTray->trace();
117     }
118 
119     virtual bool handleTimer(YTimer *timer);
120     virtual void updateToolTip();
121 };
122 YXTrayProxy* YXTrayProxy::singleton;
123 
YXTrayProxy(const YAtom & atom,YXTray * tray)124 YXTrayProxy::YXTrayProxy(const YAtom& atom, YXTray *tray):
125     _NET_SYSTEM_TRAY_S0(atom),
126     fTray(tray),
127     error_code(0),
128     request_code(0)
129 {
130     singleton = this;
131     addStyle(wsNoExpose | wsToolTipping);
132     setTitle("YXTrayProxy");
133     if (isExternal()) {
134         long orientation = SYSTEM_TRAY_ORIENTATION_HORZ;
135         setProperty(_XA_NET_SYSTEM_TRAY_ORIENTATION, XA_CARDINAL, orientation);
136 
137         unsigned long visualid = xapp->visual()->visualid;
138         setProperty(_XA_NET_SYSTEM_TRAY_VISUAL, XA_VISUALID, visualid);
139     }
140 
141     XSetSelectionOwner(xapp->display(), _NET_SYSTEM_TRAY_S0,
142                        handle(), CurrentTime);
143 
144     XClientMessageEvent xev = {};
145     xev.type = ClientMessage;
146     xev.window = desktop->handle();
147     xev.message_type = _XA_MANAGER;
148     xev.format = 32;
149     xev.data.l[0] = CurrentTime;
150     xev.data.l[1] = _NET_SYSTEM_TRAY_S0;
151     xev.data.l[2] = handle();
152 
153     xapp->send(xev, desktop->handle(), StructureNotifyMask);
154 }
155 
~YXTrayProxy()156 YXTrayProxy::~YXTrayProxy() {
157     Window w = XGetSelectionOwner(xapp->display(), _NET_SYSTEM_TRAY_S0);
158     if (w == handle()) {
159         XSetSelectionOwner(xapp->display(), _NET_SYSTEM_TRAY_S0,
160                            None, CurrentTime);
161     }
162 }
163 
beginMessage(Window win,long msec,long size,long ident)164 void YXTrayProxy::beginMessage(Window win, long msec, long size, long ident) {
165     if (trace())
166         tlog("systray begin message 0x%08lx: ms=%ld, sz=%ld, id=%ld",
167                 win, msec, size, ident);
168     if (messages.getCount() < MaxTrayNumMessages) {
169         messages.append(new TrayMessage(win, msec, size, ident));
170     }
171 }
172 
cancelMessage(Window win,long ident)173 void YXTrayProxy::cancelMessage(Window win, long ident) {
174     if (trace())
175         tlog("systray cancel message 0x%08lx: id=%ld", win, ident);
176     bool change = false;
177     for (IterType iter = messages.reverseIterator(); ++iter; ) {
178         if (iter->window == win && iter->ident == ident) {
179             change |= (iter->offset > 0);
180             iter.remove();
181         }
182     }
183     if (change)
184         updateToolTip();
185 }
186 
messageData(Window win,const char * bytes,long len)187 void YXTrayProxy::messageData(Window win, const char* bytes, long len) {
188     if (trace())
189         tlog("systray message data 0x%08lx: len=%ld", win, len);
190     bool change = false;
191     for (IterType iter = messages.reverseIterator(); ++iter; ) {
192         if (iter->window == win) {
193             change = iter->append(bytes, len);
194             break;
195         }
196     }
197     if (change)
198         updateToolTip();
199 }
200 
expireMessages()201 void YXTrayProxy::expireMessages() {
202     timeval now = monotime();
203     for (IterType iter = messages.reverseIterator(); ++iter; ) {
204         if (iter->finish < now) {
205             iter.remove();
206         }
207     }
208 }
209 
updateToolTip()210 void YXTrayProxy::updateToolTip() {
211     MSG(("YXTrayProxy::updateToolTip"));
212     long size = 0;
213     if (messages.nonempty()) {
214         expireMessages();
215         for (IterType iter = messages.iterator(); ++iter; ) {
216             if (iter->offset > 0) {
217                 size += 2 + iter->offset;
218                 if (size > MaxTrayTooltipSize)
219                     break;
220             }
221         }
222     }
223     if (size == 0) {
224         if (toolTip != null) {
225             toolTip = null;
226             setToolTip(null);
227         }
228         if (fUpdateTimer)
229             fUpdateTimer = null;
230         return;
231     }
232 
233     long len = 0;
234     csmart text(new char[3 + size]);
235     for (IterType iter = messages.iterator(); ++iter; ) {
236         if (size < len + iter->offset) {
237             break;
238         }
239         else if (iter->offset > 0) {
240             memcpy(text + len, iter->bytes, iter->offset);
241             len += iter->offset;
242             if (text[len-1] != '\n')
243                 text[len++] = '\n';
244             text[len++] = '\n';
245         }
246     }
247     text[len] = '\0';
248 
249     mstring newTip(text, int(len));
250     if (toolTip != newTip) {
251         toolTip = newTip;
252         setToolTip(newTip);
253         if ( ! fUpdateTimer)
254             fUpdateTimer->setTimer(500L, this, false);
255     }
256     if (false == fUpdateTimer->isRunning()) {
257         fUpdateTimer->startTimer();
258     }
259 }
260 
handleTimer(YTimer * timer)261 bool YXTrayProxy::handleTimer(YTimer *timer) {
262     MSG(("YXTrayProxy::handleTimer %s %ld",
263         boolstr(timer == fUpdateTimer), timer->getInterval()));
264 
265     if (timer == fUpdateTimer) {
266         updateToolTip();
267         return false;
268     }
269 
270     DockIter dock = fDockRequests.iterator();
271     while (++dock && timer != dock->timer);
272     if (dock) {
273         fTray->trayRequestDock(dock->window, dock->title);
274         dock.remove();
275     }
276 
277     return false;
278 }
279 
enableBackingStore(Window win)280 bool YXTrayProxy::enableBackingStore(Window win) {
281     XWindowAttributes attr;
282     bool okay = XGetWindowAttributes(xapp->display(), win, &attr);
283     if (okay && attr.backing_store == NotUseful) {
284         XSetWindowAttributes xswa;
285         xswa.backing_store = WhenMapped;
286         XChangeWindowAttributes(xapp->display(), win, CWBackingStore, &xswa);
287     }
288     return okay;
289 }
290 
requestDock(Window win)291 bool YXTrayProxy::requestDock(Window win) {
292     mstring title(fetchTitle(win));
293     MSG(("systemTrayRequestDock 0x%lX, title \"%s\"", win, title.c_str()));
294 
295     if (title == null && (error_code == BadWindow || windowDestroyed(win))) {
296         MSG(("Ignoring tray request for unknown window 0x%08lX", win));
297         return false;
298     }
299 
300     /*
301      * Some tray apps (GTK) sometimes fail to respond to expose events.
302      * As an experiment, enable backing store for icons to mitigate that.
303      */
304     if (enableBackingStore(win) == false) {
305         MSG(("Cannot get attributes for dock window 0x%08lX", win));
306         return false;
307     }
308 
309     if (title == "Pidgin") {
310         long delay = 200L + 25L * fDockRequests.getCount();
311         YTimer* tm = new YTimer(delay, this, true, true);
312         fDockRequests.append(new DockRequest(win, tm, title));
313         return true;
314     }
315 
316     return fTray->trayRequestDock(win, title);
317 }
318 
fetchTitle(Window win)319 mstring YXTrayProxy::fetchTitle(Window win) {
320     mstring title;
321     xsmart<char> name;
322     error_code = request_code = 0;
323     if (XFetchName(xapp->display(), win, &name)) {
324         title = (char *) name;
325     }
326     else if (error_code == BadWindow) {
327         return null;
328     }
329     if (title.isEmpty()) {
330         XTextProperty text = {};
331         if (XGetTextProperty(xapp->display(), win, &text, _XA_NET_WM_NAME)) {
332             title = reinterpret_cast<char *>(text.value);
333             XFree(text.value);
334         }
335         else if (error_code == BadWindow) {
336             return null;
337         }
338     }
339     if (title.isEmpty()) {
340         XClassHint hint;
341         if (XGetClassHint(xapp->display(), win, &hint)) {
342             title = hint.res_name;
343             XFree(hint.res_name);
344             XFree(hint.res_class);
345         }
346         else if (error_code == BadWindow) {
347             return null;
348         }
349     }
350     return title;
351 }
352 
handleError(XErrorEvent * e)353 void YXTrayProxy::handleError(XErrorEvent* e) {
354     request_code = e->request_code;
355     error_code = e->error_code;
356 
357     if (trace()) {
358         Display* display = xapp->display();
359         char message[80], req[80], number[80];
360 
361         snprintf(number, 80, "%d", e->request_code);
362         XGetErrorDatabaseText(display, "XRequest",
363                               number, "",
364                               req, sizeof(req));
365         if (req[0] == 0)
366             snprintf(req, 80, "[request_code=%d]", e->request_code);
367 
368         if (XGetErrorText(display, e->error_code, message, 80) != Success)
369             *message = '\0';
370 
371         tlog("systray %s(0x%08lx): %s", req, e->resourceid, message);
372     }
373 }
374 
dockError(Display * disp,XErrorEvent * e)375 int YXTrayProxy::dockError(Display *disp, XErrorEvent* e) {
376     if (singleton) {
377         singleton->handleError(e);
378     }
379     // Ignore bad dock attempts.
380     return Success;
381 }
382 
handleClientMessage(const XClientMessageEvent & message)383 void YXTrayProxy::handleClientMessage(const XClientMessageEvent &message) {
384     const Window window = message.window;
385     unsigned long type = message.message_type;
386 
387     if (type == _XA_NET_SYSTEM_TRAY_OPCODE) {
388         const long opcode = message.data.l[1];
389         if (opcode == SYSTEM_TRAY_REQUEST_DOCK) {
390             const Window dock = message.data.l[2];
391             XErrorHandler old = XSetErrorHandler(dockError);
392             error_code = request_code = 0;
393             requestDock(dock);
394             XSetErrorHandler(old);
395         }
396         else if (opcode == SYSTEM_TRAY_BEGIN_MESSAGE) {
397             const long milli = message.data.l[2];
398             const long bytes = message.data.l[3];
399             const long ident = message.data.l[4];
400             beginMessage(window, milli, bytes, ident);
401         }
402         else if (opcode == SYSTEM_TRAY_CANCEL_MESSAGE) {
403             const long ident = message.data.l[2];
404             cancelMessage(window, ident);
405         }
406         else if (trace()) {
407             tlog("systray ignoring unknown opcode %ld from 0x%08lx",
408                  opcode, window);
409         }
410     }
411     else if (type == _XA_NET_SYSTEM_TRAY_MESSAGE_DATA) {
412         const long length = long(sizeof message.data.b);
413         messageData(window, message.data.b, length);
414     }
415     else if (trace()) {
416         tlog("systray unknown client message type %ld from 0x%08lx",
417              type, window);
418     }
419 }
420 
YXTrayEmbedder(YXTray * tray,Window win,Window ldr,mstring title)421 YXTrayEmbedder::YXTrayEmbedder(YXTray *tray, Window win, Window ldr, mstring title):
422     YWindow(tray),
423     fVisible(false),
424     fTray(tray),
425     fClient(new YXEmbedClient(this, this, win)),
426     fLeader(Elvis(ldr, win)),
427     fTitle(title),
428     fDamage(None),
429     fComposing(xapp->alpha() && composite.supported &&
430                xapp->format() && damage.supported),
431     fOrder(getOrder(fTitle))
432 {
433     if (fClient->destroyed())
434         return;
435 
436     setStyle(wsManager | wsNoExpose);
437     setParentRelative();
438     setTitle("YXTrayEmbedder");
439 
440     fClient->setBorderWidth(0);
441     XAddToSaveSet(xapp->display(), win);
442     fClient->reparent(this, 0, 0);
443 
444     if (composing()) {
445         fDamage = XDamageCreate(xapp->display(), win, XDamageReportNonEmpty);
446         XCompositeRedirectWindow(xapp->display(), win, CompositeRedirectManual);
447     }
448 
449     if (xapp->alpha() == false)
450         fClient->setParentRelative();
451 }
452 
realise()453 void YXTrayEmbedder::realise() {
454     client()->show();
455     client()->infoMapped();
456     client()->sendNotify();
457     client()->sendActivate();
458 }
459 
~YXTrayEmbedder()460 YXTrayEmbedder::~YXTrayEmbedder() {
461     if (false == fClient->destroyed()) {
462         if (fDamage) {
463             XDamageDestroy(xapp->display(), fDamage);
464             fDamage = None;
465         }
466         fClient->hide();
467         fClient->reparent(desktop, 0, 0);
468     }
469     delete fClient;
470 }
471 
trace() const472 bool YXTrayEmbedder::trace() const {
473     return fTray->trace();
474 }
475 
damagedClient()476 void YXTrayEmbedder::damagedClient() {
477     if (trace())
478         tlog("systray damage repaint 0x%08lx", fClient->handle());
479     repaint();
480 }
481 
detach()482 void YXTrayEmbedder::detach() {
483     if (false == fClient->destroyed()) {
484         if (fDamage) {
485             XDamageDestroy(xapp->display(), fDamage);
486             fDamage = None;
487         }
488         XAddToSaveSet(xapp->display(), fClient->handle());
489         fClient->reparent(desktop, 0, 0);
490         fClient->hide();
491         XRemoveFromSaveSet(xapp->display(), fClient->handle());
492         if (trace())
493             tlog("systray detached dock window 0x%08lx", fClient->handle());
494     }
495 }
496 
destroyedClient(Window win)497 bool YXTrayEmbedder::destroyedClient(Window win) {
498     fDamage = None;
499     return fTray->destroyedClient(win);
500 }
501 
handleClientUnmap(Window win)502 void YXTrayEmbedder::handleClientUnmap(Window win) {
503     if (trace())
504         tlog("systray client unmaps  0x%08lx when %s",
505                 win, fVisible ? "visible" : "unmapped");
506     if (fVisible)
507         fTray->showClient(win, false);
508     else if (client()->testDestroyed())
509         fTray->destroyedClient(client_handle());
510 }
511 
handleClientMap(Window win)512 void YXTrayEmbedder::handleClientMap(Window win) {
513     if (trace())
514         tlog("systray client map window 0x%08lx", win);
515     fClient->show();
516     fTray->showClient(win, true);
517 }
518 
paint(Graphics & g,const YRect & r)519 void YXTrayEmbedder::paint(Graphics &g, const YRect& r) {
520     extern ref<YPixmap> taskbackPixmap;
521     if (taskbackPixmap != null) {
522         g.fillPixmap(taskbackPixmap,
523                      r.x(), r.y(), r.width(), r.height(),
524                      x() + r.x(), y() + r.y() + parent()->y());
525     }
526     else {
527         g.setColor(taskBarBg);
528         g.fillRect(r.x(), r.y(), r.width(), r.height());
529     }
530 
531     if (composing()) {
532         XDamageSubtract(xapp->display(), fDamage, None, None);
533         Picture source = fClient->createPicture();
534         Picture target = g.picture();
535         if (source && target) {
536             XRenderComposite(xapp->display(), PictOpOver,
537                              source, None, target,
538                              0, 0, 0, 0, 0, 0,
539                              width(), height());
540         }
541         if (source)
542             XRenderFreePicture(xapp->display(), source);
543     }
544 }
545 
configure(const YRect2 & r)546 void YXTrayEmbedder::configure(const YRect2 &r) {
547     if (r.resized()) {
548         repaint();
549         fClient->setGeometry(YRect(0, 0, r.width(), r.height()));
550     }
551 }
552 
repaint()553 void YXTrayEmbedder::repaint() {
554     GraphicsBuffer(this).paint();
555 }
556 
handleConfigureRequest(const XConfigureRequestEvent & configureRequest)557 void YXTrayEmbedder::handleConfigureRequest(const XConfigureRequestEvent &configureRequest)
558 {
559     fTray->handleConfigureRequest(configureRequest);
560 }
561 
handleClientMessage(const XClientMessageEvent & message)562 void YXTrayEmbedder::handleClientMessage(const XClientMessageEvent& message)
563 {
564     if (trace())
565         logClientMessage(message);
566 }
567 
handleMapRequest(const XMapRequestEvent & mapRequest)568 void YXTrayEmbedder::handleMapRequest(const XMapRequestEvent &mapRequest) {
569     fClient->show();
570     fTray->showClient(mapRequest.window, true);
571 }
572 
YXTray(YXTrayNotifier * notifier,bool internal,const YAtom & atom,YWindow * aParent,bool drawBevel)573 YXTray::YXTray(YXTrayNotifier *notifier,
574                bool internal,
575                const YAtom& atom,
576                YWindow *aParent,
577                bool drawBevel):
578     YWindow(aParent),
579     fTrayProxy(nullptr),
580     fNotifier(notifier),
581     fLocked(false),
582     fRunProxy(internal == false),
583     fTrace(YTrace::traces("systray")),
584     fDrawBevel(drawBevel)
585 {
586     DBG { fTrace = true; }
587     addStyle(wsNoExpose);
588     setTitle("YXTray");
589     setParentRelative();
590     setSize(1, trayIconMaxHeight + fDrawBevel);
591     fTrayProxy = new YXTrayProxy(atom, this);
592     regainTrayWindows();
593 }
594 
~YXTray()595 YXTray::~YXTray() {
596     delete fTrayProxy; fTrayProxy = nullptr;
597 }
598 
getScaleSize(unsigned & w,unsigned & h)599 void YXTray::getScaleSize(unsigned& w, unsigned& h)
600 {
601     // check if max_width / max_height < width / height. */
602     if (h * trayIconMaxWidth < w * trayIconMaxHeight) {
603         // icon is wide.
604         if (w != trayIconMaxWidth) {
605             h = (trayIconMaxWidth * h + (w / 2)) / w;
606             w = trayIconMaxWidth;
607         }
608     } else {
609         // icon is tall.
610         if (h != trayIconMaxHeight) {
611             w = (trayIconMaxHeight * w + (h / 2)) / h;
612             h = trayIconMaxHeight;
613         }
614     }
615 }
616 
getLeader(Window win)617 Window YXTray::getLeader(Window win) {
618     YProperty prop(win, _XA_WM_CLIENT_LEADER, F32, 1L, XA_WINDOW);
619     return prop ? *prop : None;
620 }
621 
trayRequestDock(Window win,mstring title)622 bool YXTray::trayRequestDock(Window win, mstring title) {
623     if (trace())
624         tlog("systray dock requested 0x%08lx \"%s\"", win, title.c_str());
625 
626     if (destroyedClient(win)) {
627         if (trace())
628             tlog("systray ignoring destroyed window 0x%08lx", win);
629         return false;
630     }
631 
632     Window leader = getLeader(win);
633     if (leader == None && windowDestroyed(win)) {
634         if (trace())
635             tlog("systray ignoring destroyed window 0x%08lx", win);
636         return false;
637     }
638 
639     YXTrayEmbedder *embed = new YXTrayEmbedder(this, win, leader, title);
640 
641     unsigned ww = embed->client()->width();
642     unsigned hh = embed->client()->height();
643 
644     if (trace())
645         tlog("systray docking window 0x%08lx size %d %d", win, ww, hh);
646 
647     /* Workaround for GTK-Apps */
648     if (ww == 0 && hh == 0)
649         ww = hh = 24;
650 
651     if (fRunProxy) {
652         // scale icons
653         getScaleSize(ww, hh);
654     }
655     embed->setSize(ww, hh);
656     embed->fVisible = true;
657 
658     int found = find(fRegained, Elvis(leader, win));
659     IterType iter = fDocked.iterator();
660     while (++iter &&
661            (iter->order() < embed->order() ||
662             (iter->order() == embed->order() &&
663              found >= 0 &&
664              inrange(find(fRegained, iter->leader()), 0, found - 1))));
665 
666     iter.insert(embed);
667     embed->realise();
668     updateTrayWindows();
669 
670     if (trace())
671         tlog("systray docked window  0x%08lx at %d", win, iter.where());
672 
673     unsigned w = max(width(), 2U * fDrawBevel) + ww;
674     unsigned h = max(height(), hh + fDrawBevel);
675     trayUpdateGeometry(w, h, true);
676     relayout(true);
677     return true;
678 }
679 
destroyedClient(Window win)680 bool YXTray::destroyedClient(Window win) {
681     bool change = false;
682     for (IterType ec = fDocked.reverseIterator(); ++ec; ) {
683         if (ec->client_handle() == win) {
684             ec.remove();
685             if (trace())
686                 tlog("systray removed window 0x%08lx from %d",
687                      win, ec.where());
688             change = true;
689         }
690     }
691     if (change) {
692         updateTrayWindows();
693         relayout(true);
694     }
695     return change;
696 }
697 
handleConfigureRequest(const XConfigureRequestEvent & request)698 void YXTray::handleConfigureRequest(const XConfigureRequestEvent &request)
699 {
700     if (trace())
701         tlog("systrace configure req 0x%08lx to w=%d h=%d",
702             request.window, request.width, request.height);
703     bool changed = false;
704     for (IterType ec = fDocked.iterator(); ++ec; ) {
705         if (ec->client_handle() == request.window) {
706             unsigned ww = request.width;
707             unsigned hh = request.height;
708             if (fRunProxy) {
709                 /* scale icons */
710                 getScaleSize(ww, hh);
711             }
712             if (ww != ec->width() || hh != ec->height())
713                 changed = true;
714             ec->setSize(ww, hh);
715         }
716     }
717     if (changed)
718         relayout(true);
719 }
720 
showClient(Window win,bool showClient)721 void YXTray::showClient(Window win, bool showClient) {
722     for (IterType ec = fDocked.iterator(); ++ec; ) {
723         if (ec->client_handle() == win) {
724             if (ec->fVisible != showClient) {
725                 ec->fVisible = showClient;
726                 if (showClient)
727                     ec->show();
728                 else
729                     ec->hide();
730                 relayout(true);
731             }
732         }
733     }
734 }
735 
detachTray()736 void YXTray::detachTray() {
737     fDocked.clear();
738 }
739 
paint(Graphics & g,const YRect &)740 void YXTray::paint(Graphics &g, const YRect &/*r*/) {
741     if (!fDrawBevel)
742         return;
743     g.setColor(taskBarBg);
744     if (trayDrawBevel && fDocked.getCount())
745         g.draw3DRect(0, 0, width() - 1, height() - 1, false);
746 }
747 
configure(const YRect2 & rect)748 void YXTray::configure(const YRect2& rect) {
749     bool enforce = (fGeometry != rect);
750     fGeometry = rect;
751     if (rect.resized() || enforce) {
752         repaint();
753         relayout(true);
754     } else {
755         relayout(false);
756     }
757 }
758 
repaint()759 void YXTray::repaint() {
760     GraphicsBuffer(this).paint();
761 }
762 
backgroundChanged()763 void YXTray::backgroundChanged() {
764     if (fDrawBevel)
765         return;
766     relayout(true);
767     repaint();
768     for (IterType ec = fDocked.iterator(); ++ec; ) {
769         /* something is not clearing which background changes */
770         XClearArea(xapp->display(), ec->client_handle(), 0, 0, 0, 0, True);
771         ec->repaint();
772     }
773 }
774 
relayout(bool enforced)775 void YXTray::relayout(bool enforced) {
776     if (fLocked)
777         return;
778     Lock lock(&fLocked);
779 
780     int aw = 0;
781     int countVisible = 0;
782     const unsigned h = trayIconMaxHeight + fDrawBevel;
783     XWindowAttributes attr;
784 
785     for (IterType ec = fDocked.reverseIterator(); ++ec; ) {
786         if (ec->client()->destroyed()) {
787             if (trace())
788                 tlog("systray destroyed %08lx", ec->client_handle());
789             ec.remove();
790             updateTrayWindows();
791             enforced = true;
792         }
793     }
794 
795     if (enforced == false)
796         return;
797 
798     for (IterType ec = rightToLeft
799                      ? fDocked.reverseIterator()
800                      : fDocked.iterator(); ++ec; ) {
801         if (false == ec->fVisible) {
802             // skip
803         }
804         else if (ec->client()->getWindowAttributes(&attr)) {
805             int eh = h - fDrawBevel;
806             int ew = ec->width();
807             int ay = fDrawBevel;
808             aw = max(int(fDrawBevel), aw);
809             ec->setGeometry(YRect(aw, ay, ew, eh));
810             ec->client()->setGeometry(YRect(0, 0, ew, eh));
811             aw += ew;
812             countVisible++;
813         }
814         else {
815             if (trace())
816                 tlog("systray sanity remove %08lx", ec->client_handle());
817             ec.remove();
818             updateTrayWindows();
819             --ec;
820         }
821     }
822     aw += fDrawBevel;
823 
824     unsigned w = aw;
825     if (fRunProxy) {
826         if (w < 1)
827             w = 1;
828     }
829     if (fDrawBevel) {
830         if (w < 4)
831             w = 0;
832     }
833     trayUpdateGeometry(w, h, countVisible > 0);
834 
835     for (IterType ec = fDocked.iterator(); ++ec; ) {
836         if (ec->fVisible)
837             ec->show();
838     }
839 
840     MSG(("clients %d width: %d, visible %s",
841          fDocked.getCount(), width(), boolstr(visible())));
842 }
843 
trayUpdateGeometry(unsigned w,unsigned h,bool visible)844 void YXTray::trayUpdateGeometry(unsigned w, unsigned h, bool visible) {
845     if (visible == false) {
846         hide();
847         w = 0;
848     }
849     MSG(("relayout %d %d : %d %d", w, h, width(), height()));
850     if (w != width() || h != height()) {
851         int ypos = (h == height()) ? y() : 1;
852         fGeometry.setRect(x() + int(width()) - int(w), ypos, w, h);
853         setGeometry(fGeometry);
854         if (visible)
855             show();
856         if (fNotifier)
857             fNotifier->trayChanged();
858     }
859 }
860 
updateTrayWindows()861 void YXTray::updateTrayWindows() {
862     const int count = fDocked.getCount();
863     Window windows[count];
864 
865     for (IterType ec = fDocked.iterator(); ++ec; )
866         windows[ec.where()] = ec->leader();
867 
868     desktop->setProperty(_XA_KDE_NET_SYSTEM_TRAY_WINDOWS, XA_WINDOW,
869                          windows, count);
870 }
871 
regainTrayWindows()872 void YXTray::regainTrayWindows() {
873     YProperty prop(desktop, _XA_KDE_NET_SYSTEM_TRAY_WINDOWS,
874                    F32, 123L, XA_WINDOW, True);
875     fRegained.clear();
876     for (Atom window : prop) {
877         fRegained += window;
878     }
879 }
880 
881 // vim: set sw=4 ts=4 et:
882