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(©);
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(©);
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