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