1 #include "config.h"
2 #include "wmdock.h"
3 #include "wmclient.h"
4 #include "wmframe.h"
5 #include "wmmgr.h"
6 #include "wmoption.h"
7 #include "ymenu.h"
8 #include "ymenuitem.h"
9 #include "yxapp.h"
10 #include "yxcontext.h"
11 #include <X11/Xatom.h>
12 
13 const char DockApp::propertyName[] = "_ICEWM_DOCKAPPS";
14 
DockApp()15 DockApp::DockApp():
16     YWindow(nullptr, None,
17             DefaultDepth(xapp->display(), xapp->screen()),
18             DefaultVisual(xapp->display(), xapp->screen()),
19             DefaultColormap(xapp->display(), xapp->screen())),
20     dragged(nullptr),
21     saveset(None),
22     intern(None),
23     center(0),
24     layered(WinLayerInvalid),
25     direction(1),
26     dragxpos(0),
27     dragypos(0),
28     restack(true),
29     isRight(true)
30 {
31     setStyle(wsOverrideRedirect | wsNoExpose);
32 }
33 
~DockApp()34 DockApp::~DockApp() {
35     hide();
36     for (int i = docks.getCount(); --i >= 0; ) {
37         docking dock = docks[i];
38         undock(i);
39         delete dock.client;
40     }
41     if (saveset) {
42         XDestroyWindow(xapp->display(), saveset);
43     }
44 }
45 
setup()46 bool DockApp::setup() {
47     extern const char* dockApps;
48     mstring config(mstring(dockApps).trim().lower());
49     if (config.isEmpty()) {
50         return false;
51     }
52 
53     for (mstring s(config), r; s.splitall(' ', &s, &r); s = r) {
54         if (s == "right") {
55             isRight = true;
56         } else if (s == "left") {
57             isRight = false;
58         } else if (s == "above") {
59             layered = WinLayerAboveDock;
60         } else if (s == "dock") {
61             layered = WinLayerDock;
62         } else if (s == "ontop") {
63             layered = WinLayerOnTop;
64         } else if (s == "normal") {
65             layered = WinLayerNormal;
66         } else if (s == "below") {
67             layered = WinLayerBelow;
68         } else if (s == "desktop") {
69             layered = WinLayerDesktop;
70         } else if (s == "center") {
71             center = 0;
72         } else if (s == "down") {
73             center = 1;
74         } else if (s == "high") {
75             center = -1;
76         }
77     }
78     if (layered == WinLayerInvalid)
79         layered = WinLayerDesktop;
80 
81     extern const char* clrNormalButton;
82     YColor bg(clrNormalButton);
83     setBackground(bg.pixel());
84     setTitle("IceDock");
85     unsigned char wmClassName[] = "icedock\0IceWM";
86     XChangeProperty(xapp->display(), handle(), XA_WM_CLASS, XA_STRING, 8,
87                     PropModeReplace, wmClassName, sizeof(wmClassName));
88     if (intern == None) {
89         intern = xapp->atom(propertyName);
90     }
91     if (intern) {
92         YProperty prop(desktop, intern, F32, 123L, XA_WINDOW, True);
93         for (Atom window : prop) {
94             recover += window;
95         }
96     }
97     return true;
98 }
99 
grabit()100 void DockApp::grabit() {
101     XGrabButton(xapp->display(), AnyButton, ControlMask, handle(), False,
102                 ButtonPressMask | ButtonReleaseMask | Button1MotionMask,
103                 GrabModeSync, GrabModeAsync, None, None);
104 }
105 
ungrab()106 void DockApp::ungrab() {
107     XUngrabButton(xapp->display(), AnyButton, ControlMask, handle());
108 }
109 
savewin()110 Window DockApp::savewin() {
111     if (saveset == None) {
112         saveset = XCreateSimpleWindow(xapp->display(), xapp->root(),
113                                       -1, -1, 1, 1, 0, None, None);
114         unsigned char wmClassName[] = "icesave\0IceWM";
115         XChangeProperty(xapp->display(), saveset, XA_WM_CLASS, XA_STRING, 8,
116                         PropModeReplace, wmClassName, sizeof(wmClassName));
117         XStoreName(xapp->display(), saveset, "IceSave");
118     }
119     return saveset;
120 }
121 
isChild(Window window)122 bool DockApp::isChild(Window window) {
123     unsigned count = 0;
124     xsmart<Window> child;
125     if (xapp->children(handle(), &child, &count)) {
126         for (unsigned i = 0; i < count; ++i) {
127             if (window == child[i]) {
128                 return true;
129             }
130         }
131     }
132     return false;
133 }
134 
dock(YFrameClient * client)135 bool DockApp::dock(YFrameClient* client) {
136     Window icon = None;
137     if (client->adopted()) {
138         if (client->isDockAppIcon()) {
139             icon = client->iconWindowHint();
140             YWindow* ptr = nullptr;
141             if (windowContext.find(icon, &ptr) && ptr && ptr != client) {
142                 YFrameClient* other = dynamic_cast<YFrameClient*>(ptr);
143                 if (other && other->adopted()) {
144                     manager->unmanageClient(other);
145                 }
146             }
147         }
148         else if (client->isDockAppWindow()) {
149             icon = client->handle();
150         }
151     }
152     if (icon) {
153         Window root;
154         int x, y;
155         unsigned w, h, border, depth;
156         if (XGetGeometry(xapp->display(), icon, &root, &x, &y,
157                          &w, &h, &border, &depth) == False) {
158             icon = None;
159         }
160         else if (w > 64 || h > 64) {
161             icon = None;
162         }
163         else if (created() == false && setup() == false) {
164             icon = None;
165         }
166     }
167     if (icon) {
168         XAddToSaveSet(xapp->display(), icon);
169         XReparentWindow(xapp->display(), icon, handle(),
170                         0, height() + 64);
171         if (isChild(icon)) {
172             XMapWindow(xapp->display(), icon);
173             if (icon != client->handle()) {
174                 XAddToSaveSet(xapp->display(), client->handle());
175                 XReparentWindow(xapp->display(), client->handle(),
176                                 savewin(), 0, 0);
177             }
178 
179             bool closing = false, forcing = false;
180             int order = ordering(client, &closing, &forcing);
181             if (closing) {
182                 client->setDocked(true);
183                 int k = docks.getCount();
184                 docks += docking(icon, client, order);
185                 revoke(k, forcing);
186                 return true;
187             }
188 
189             int found = find(recover, client->handle());
190             int k = docks.getCount();
191             while (k > 0 &&
192                    (order < docks[k-1].order ||
193                     (order == docks[k-1].order &&
194                      found >= 0 &&
195                      !inrange(find(recover, docks[k-1].client->handle()),
196                               0, found))))
197             {
198                 k--;
199             }
200             docks.insert(k, docking(icon, client, order));
201 
202             client->setDocked(true);
203             direction = +1;
204             retime();
205         }
206         else {
207             XRemoveFromSaveSet(xapp->display(), icon);
208             icon = None;
209         }
210     }
211     return bool(icon);
212 }
213 
ordering(YFrameClient * client,bool * startClose,bool * forced)214 int DockApp::ordering(YFrameClient* client, bool* startClose, bool* forced) {
215     char* name = client->classHint()->res_name;
216     if (nonempty(name)) {
217         char* base = const_cast<char*>(my_basename(name));
218         if (nonempty(base) && base != name) {
219             char* copy = strdup(base);
220             if (copy) {
221                 XFree(name);
222                 name = copy;
223                 client->classHint()->res_name = copy;
224             }
225         }
226     }
227 
228     xsmart<char> copy;
229     if (isEmpty(name)) {
230         client->fetchTitle(&copy);
231         name = copy;
232     }
233 
234     int order = 0;
235     if (nonempty(name)) {
236         WindowOption opt(name);
237         if (hintOptions)
238             hintOptions->mergeWindowOption(opt, name, true);
239         if (defOptions)
240             defOptions->mergeWindowOption(opt, name, false);
241         order = opt.order;
242         *startClose = hasbit(opt.option_mask, YFrameWindow::foClose)
243                        && hasbit(opt.options, YFrameWindow::foClose);
244         *forced = hasbit(opt.option_mask, YFrameWindow::foForcedClose)
245                    && hasbit(opt.options, YFrameWindow::foForcedClose);
246     } else {
247         *startClose = false;
248     }
249     return order;
250 }
251 
handleTimer(YTimer * t)252 bool DockApp::handleTimer(YTimer* t) {
253     bool restart = false;
254     if (t == timer) {
255         if (manager->isRunning()) {
256             adapt();
257         } else {
258             restart = true;
259         }
260     }
261     return restart;
262 }
263 
adapt()264 void DockApp::adapt() {
265     if (docks.nonempty()) {
266         int mx, my, Mx, My;
267         manager->getWorkArea(&mx, &my, &Mx, &My);
268         int rows = min(docks.getCount(), (My - my) / 64);
269         int cols = (docks.getCount() + (rows - 1)) / rows;
270         rows = (docks.getCount() + (cols - 1)) / cols;
271         int xpos = isRight ? Mx - cols * 64 : 0;
272         int ypos = (center == -1) ? 0
273                  : (center == +1) ? (My - rows * 64)
274                  : my + (My - my - rows * 64) / 2;
275         setGeometry(YRect(xpos, ypos, cols * 64, rows * 64));
276         for (int k = 0; k < docks.getCount(); k++) {
277             int i = (direction < 0) ? (docks.getCount() - 1 - k) : k;
278             int x = 64 * (cols - 1 - i / rows);
279             int y = 64 * (i % rows);
280             if (docks[i].window == docks[i].client->handle()) {
281                 x += (64 - min(64, int(docks[i].client->width()))) / 2;
282                 y += (64 - min(64, int(docks[i].client->height()))) / 2;
283             }
284             XMoveWindow(xapp->display(), docks[i].window, x, y);
285             XMapWindow(xapp->display(), docks[i].window);
286         }
287 #ifdef CONFIG_SHAPE_RR
288         if (false && shapes.supported) {
289             const int count = docks.getCount();
290             XRectangle rect[count];
291             for (int i = 0; i < count; ++i) {
292                 rect[i].x = 64 * (cols - 1 - i / rows);
293                 rect[i].y = 64 * (i % rows);
294                 rect[i].width = 64;
295                 rect[i].height = 64;
296             }
297             XShapeCombineRectangles(xapp->display(), handle(),
298                                     ShapeBounding, 0, 0, rect,
299                                     count, ShapeSet, Unsorted);
300         }
301 #endif
302 #ifdef CONFIG_SHAPE_MM
303         if (false && shapes.supported) {
304             XRectangle full;
305             full.x = 0;
306             full.y = 0;
307             full.width = width();
308             full.height = height();
309             XShapeCombineRectangles(xapp->display(), handle(),
310                                     ShapeBounding, 0, 0, &full, 1,
311                                     ShapeSet, Unsorted);
312             for (int i = 0; i < docks.getCount(); i++) {
313                 XShapeCombineShape(xapp->display(), handle(), ShapeBounding,
314                                    64 * (i / rows), 64 * (i % rows),
315                                    docks[i].window, ShapeBounding,
316                                    i == 0 ? ShapeSet : ShapeUnion);
317             }
318         }
319 #endif
320         if (visible() == false) {
321             show();
322             grabit();
323             if (restack) {
324                 restack = false;
325                 manager->restackWindows();
326             }
327         }
328         proper();
329     }
330     else if (visible()) {
331         ungrab();
332         hide();
333         proper();
334     }
335     if (timer)
336         timer = null;
337 }
338 
proper()339 void DockApp::proper() {
340     if (intern == None) {
341         intern = xapp->atom(propertyName);
342     }
343     if (intern) {
344         const int count = docks.getCount();
345         if (count) {
346             Atom atoms[count];
347             for (int i = 0; i < count; ++i) {
348                 atoms[i] = Atom(docks[i].client->handle());
349             }
350             desktop->setProperty(intern, XA_WINDOW, atoms, count);
351         } else {
352             desktop->deleteProperty(intern);
353         }
354     }
355 }
356 
undock(int index)357 void DockApp::undock(int index) {
358     docking dock(docks[index]);
359     if (dock.client->destroyed() == false) {
360         if (dock.client->handle() == dock.window) {
361             XReparentWindow(xapp->display(), dock.window,
362                             xapp->root(), 0, 0);
363             XRemoveFromSaveSet(xapp->display(), dock.window);
364         } else {
365             XUnmapWindow(xapp->display(), dock.window);
366             XReparentWindow(xapp->display(), dock.window,
367                             xapp->root(), 0, 0);
368             XRemoveFromSaveSet(xapp->display(), dock.window);
369             XReparentWindow(xapp->display(), dock.client->handle(),
370                             xapp->root(), 0, 0);
371             XRemoveFromSaveSet(xapp->display(), dock.client->handle());
372             XMapWindow(xapp->display(), dock.client->handle());
373         }
374         if (dragged == dock.client) {
375             dragged = nullptr;
376         }
377     }
378     docks.remove(index);
379 }
380 
undock(YFrameClient * client)381 bool DockApp::undock(YFrameClient* client) {
382     for (int i = docks.getCount(); --i >= 0; ) {
383         if (docks[i].client == client) {
384             undock(i);
385             direction = +1;
386             retime();
387             return true;
388         }
389     }
390     return false;
391 }
392 
revoke(int k,bool kill)393 void DockApp::revoke(int k, bool kill) {
394     if (inrange(k, 0, docks.getCount() - 1)) {
395         docking dock(docks[k]);
396         XUnmapWindow(xapp->display(), dock.window);
397         if (kill || !dock.client->protocol(YFrameClient::wpDeleteWindow)) {
398             XDestroyWindow(xapp->display(), dock.client->handle());
399             if (dock.window != dock.client->handle())
400                 XDestroyWindow(xapp->display(), dock.window);
401         } else {
402             dock.client->sendPing();
403             dock.client->sendDelete();
404         }
405         docks.remove(k);
406         retime();
407     }
408 }
409 
actionPerformed(YAction action,unsigned modifiers)410 void DockApp::actionPerformed(YAction action, unsigned modifiers) {
411     revoke(action.ident() - 1, false);
412 }
413 
handlePopDown(YPopupWindow * popup)414 void DockApp::handlePopDown(YPopupWindow* popup) {
415     if (menu == popup)
416         menu = null;
417     if (docks.nonempty())
418         grabit();
419 }
420 
handleButton(const XButtonEvent & button)421 void DockApp::handleButton(const XButtonEvent& button) {
422     if (hasbit(button.state, ControlMask)) {
423         XAllowEvents(xapp->display(), AsyncPointer, CurrentTime);
424     }
425     YWindow::handleButton(button);
426 }
427 
handleClick(const XButtonEvent & button,int count)428 void DockApp::handleClick(const XButtonEvent& button, int count) {
429     int click = int(button.button) * int(hasbit(button.state, ControlMask));
430     if (click == Button2 && count == 1) {
431         int k = 0;
432         for (docking dock : docks) {
433             if (dock.window == button.subwindow) {
434                 revoke(k, hasbit(button.state, ShiftMask));
435                 return;
436             }
437             ++k;
438         }
439     }
440     if (click == Button3 && count == 1) {
441         menu = null;
442         int rows = height() / 64;
443         // int cols = width() / 64;
444         int k = 0, select = -1, separators = 0;
445         for (docking dock : docks) {
446             const char* name = dock.client->classHint()->res_name;
447             xsmart<char> copy;
448             if (isEmpty(name)) {
449                 dock.client->fetchTitle(&copy);
450                 name = copy;
451             }
452             mstring number;
453             if (isEmpty(name)) {
454                 number = mstring(k);
455                 name = number.c_str();
456             }
457             menu->addItem(name, -1, null, YAction(EAction(k + 1)))
458                 ->setChecked(true);
459             if (dock.window == button.subwindow) {
460                 select = k + separators;
461             }
462             if (++k % rows == 0 && k < docks.getCount()) {
463                 menu->addSeparator();
464                 ++separators;
465             }
466         }
467         menu->setActionListener(this);
468         menu->popup(nullptr, nullptr, this,
469                     button.x_root, button.y_root,
470                     YPopupWindow::pfCanFlipVertical |
471                     YPopupWindow::pfCanFlipHorizontal);
472         if (select >= 0) {
473             menu->focusItem(select);
474         }
475     }
476     if (click == Button4) {
477         if (docks.getCount() > 1) {
478             docking dock(docks[0]);
479             docks.remove(0);
480             docks += dock;
481             direction = +1;
482             retime();
483         }
484     }
485     if (click == Button5) {
486         if (docks.getCount() > 1) {
487             docking dock(docks.last());
488             docks.pop();
489             docks.insert(0, dock);
490             direction = -1;
491             retime();
492         }
493     }
494 }
495 
handleBeginDrag(const XButtonEvent & down,const XMotionEvent & move)496 bool DockApp::handleBeginDrag(const XButtonEvent& down, const XMotionEvent& move) {
497     dragged = nullptr;
498     if (down.button == Button1 && hasbit(down.state, ControlMask)) {
499         if (down.subwindow && move.subwindow &&
500             down.subwindow == move.subwindow)
501         {
502             for (const docking& dock : docks) {
503                 if (dock.window == down.subwindow) {
504                     XWindowAttributes attr;
505                     if (XGetWindowAttributes(xapp->display(),
506                                              dock.window, &attr))
507                     {
508                         dragged = dock.client;
509                         dragxpos = attr.x;
510                         dragypos = attr.y;
511                         XRaiseWindow(xapp->display(), dock.window);
512                         handleDrag(down, move);
513                         break;
514                     }
515                 }
516             }
517         }
518     }
519     return dragged;
520 }
521 
handleDrag(const XButtonEvent & down,const XMotionEvent & move)522 void DockApp::handleDrag(const XButtonEvent& down, const XMotionEvent& move) {
523     if (dragged) {
524         for (const docking& dock : docks) {
525             if (dock.client == dragged) {
526                 int x = dragxpos + (move.x_root - down.x_root);
527                 int y = dragypos + (move.y_root - down.y_root);
528                 XMoveWindow(xapp->display(), dock.window, x, y);
529                 break;
530             }
531         }
532     }
533 }
534 
handleEndDrag(const XButtonEvent & down,const XButtonEvent & up)535 void DockApp::handleEndDrag(const XButtonEvent& down, const XButtonEvent& up) {
536     if (dragged) {
537         XWindowAttributes attr = {};
538         int x = dragxpos + (up.x_root - down.x_root);
539         int y = dragypos + (up.y_root - down.y_root);
540         int d = -1;
541         int a = -1;
542         for (int i = 0; i < docks.getCount(); ++i) {
543             if (docks[i].client == dragged) {
544                 d = i;
545             }
546             else if (a == -1
547                 && XGetWindowAttributes(xapp->display(),
548                                         docks[i].window, &attr)
549                 && attr.x / 64 == (x + 32) / 64
550                 && attr.y / 64 == (y + 32) / 64) {
551                 a = i;
552             }
553         }
554         if (d >= 0 && a >= 0) {
555             XMoveWindow(xapp->display(), docks[d].window,
556                         (attr.x / 64) * 64 + dragxpos % 64,
557                         (attr.y / 64) * 64 + dragypos % 64);
558             docking copy(docks[d]);
559             docks.remove(d);
560             docks.insert(a, copy);
561             proper();
562             retime();
563         }
564         else if (d >= 0) {
565             XMoveWindow(xapp->display(), docks[d].window,
566                         dragxpos, dragypos);
567         }
568         dragged = nullptr;
569     }
570 }
571 
572