]> Dogcows Code - chaz/openbox/blob - src/Workspace.cc
much awesome support for special windows like panels/desktops.
[chaz/openbox] / src / Workspace.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Workspace.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2002 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000 Brad Hughes (bhughes@tcac.net)
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the
11 // Software is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23
24 #ifdef HAVE_CONFIG_H
25 # include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31
32 #ifdef HAVE_STDIO_H
33 # include <stdio.h>
34 #endif // HAVE_STDIO_H
35
36 #ifdef HAVE_STRING_H
37 # include <string.h>
38 #endif // HAVE_STRING_H
39 }
40
41 #include <assert.h>
42
43 #include <functional>
44 #include <string>
45
46 using std::string;
47
48 #include "i18n.hh"
49 #include "blackbox.hh"
50 #include "Clientmenu.hh"
51 #include "Netizen.hh"
52 #include "Screen.hh"
53 #include "Toolbar.hh"
54 #include "Util.hh"
55 #include "Window.hh"
56 #include "Workspace.hh"
57 #include "Windowmenu.hh"
58 #include "XAtom.hh"
59
60
61 Workspace::Workspace(BScreen *scrn, unsigned int i) {
62 screen = scrn;
63 xatom = screen->getBlackbox()->getXAtom();
64
65 cascade_x = cascade_y = 32;
66
67 id = i;
68
69 clientmenu = new Clientmenu(this);
70
71 lastfocus = (BlackboxWindow *) 0;
72
73 readName();
74 }
75
76
77 void Workspace::addWindow(BlackboxWindow *w, bool place) {
78 assert(w != 0);
79
80 if (place) placeWindow(w);
81
82 stackingList.push_front(w);
83
84 if (w->isNormal()) {
85 w->setWorkspace(id);
86 w->setWindowNumber(windowList.size());
87
88 windowList.push_back(w);
89
90 clientmenu->insert(w->getTitle());
91 clientmenu->update();
92
93 screen->updateNetizenWindowAdd(w->getClientWindow(), id);
94 }
95
96 if (! w->isDesktop())
97 raiseWindow(w);
98 else
99 lowerWindow(w);
100 }
101
102
103 void Workspace::removeWindow(BlackboxWindow *w) {
104 assert(w != 0);
105
106 stackingList.remove(w);
107
108 // pass focus to the next appropriate window
109 if ((w->isFocused() || w == lastfocus) &&
110 ! screen->getBlackbox()->doShutdown()) {
111 focusFallback(w);
112
113 // if the window is sticky, then it needs to be removed on all other
114 // workspaces too!
115 if (w->isStuck()) {
116 for (unsigned int i = 0; i < screen->getWorkspaceCount(); ++i)
117 if (i != id)
118 screen->getWorkspace(i)->focusFallback(w);
119 }
120 }
121
122 if (! w->isNormal()) return;
123
124 windowList.remove(w);
125 clientmenu->remove(w->getWindowNumber());
126 clientmenu->update();
127
128 screen->updateNetizenWindowDel(w->getClientWindow());
129
130 BlackboxWindowList::iterator it = windowList.begin();
131 const BlackboxWindowList::iterator end = windowList.end();
132 unsigned int i = 0;
133 for (; it != end; ++it, ++i)
134 (*it)->setWindowNumber(i);
135
136 if (i == 0)
137 cascade_x = cascade_y = 32;
138 }
139
140
141 void Workspace::focusFallback(const BlackboxWindow *old_window) {
142 BlackboxWindow *newfocus = 0;
143
144 if (id == screen->getCurrentWorkspaceID()) {
145 // The window is on the visible workspace.
146
147 // if it's a transient, then try to focus its parent
148 if (old_window && old_window->isTransient()) {
149 newfocus = old_window->getTransientFor();
150
151 if (! newfocus ||
152 newfocus->isIconic() || // do not focus icons
153 newfocus->getWorkspaceNumber() != id || // or other workspaces
154 ! newfocus->setInputFocus())
155 newfocus = 0;
156 }
157
158 if (! newfocus) {
159 BlackboxWindowList::iterator it = stackingList.begin(),
160 end = stackingList.end();
161 for (; it != end; ++it) {
162 BlackboxWindow *tmp = *it;
163 if (tmp && tmp->isNormal() && tmp->setInputFocus()) {
164 // we found our new focus target
165 newfocus = tmp;
166 break;
167 }
168 }
169 }
170
171 screen->getBlackbox()->setFocusedWindow(newfocus);
172 } else {
173 // The window is not on the visible workspace.
174
175 if (old_window && lastfocus == old_window) {
176 // The window was the last-focus target, so we need to replace it.
177 BlackboxWindow *win = (BlackboxWindow*) 0;
178 if (! stackingList.empty())
179 win = stackingList.front();
180 setLastFocusedWindow(win);
181 }
182 }
183 }
184
185
186 void Workspace::showAll(void) {
187 std::for_each(stackingList.begin(), stackingList.end(),
188 std::mem_fun(&BlackboxWindow::show));
189 }
190
191
192 void Workspace::hideAll(void) {
193 // withdraw in reverse order to minimize the number of Expose events
194
195 BlackboxWindowList lst(stackingList.rbegin(), stackingList.rend());
196
197 BlackboxWindowList::iterator it = lst.begin();
198 const BlackboxWindowList::iterator end = lst.end();
199 for (; it != end; ++it) {
200 BlackboxWindow *bw = *it;
201 if (! bw->isStuck())
202 bw->withdraw();
203 }
204 }
205
206
207 void Workspace::removeAll(void) {
208 while (! windowList.empty())
209 windowList.front()->iconify();
210 }
211
212
213 /*
214 * returns the number of transients for win, plus the number of transients
215 * associated with each transient of win
216 */
217 static int countTransients(const BlackboxWindow * const win) {
218 int ret = win->getTransients().size();
219 if (ret > 0) {
220 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
221 for (it = win->getTransients().begin(); it != end; ++it) {
222 ret += countTransients(*it);
223 }
224 }
225 return ret;
226 }
227
228
229 /*
230 * puts the transients of win into the stack. windows are stacked above
231 * the window before it in the stackvector being iterated, meaning
232 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
233 * stack[1], etc...
234 */
235 void Workspace::raiseTransients(const BlackboxWindow * const win,
236 StackVector::iterator &stack) {
237 if (win->getTransients().size() == 0) return; // nothing to do
238
239 // put win's transients in the stack
240 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
241 for (it = win->getTransients().begin(); it != end; ++it) {
242 *stack++ = (*it)->getFrameWindow();
243 screen->updateNetizenWindowRaise((*it)->getClientWindow());
244
245 if (! (*it)->isIconic()) {
246 Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
247 wkspc->stackingList.remove((*it));
248 wkspc->stackingList.push_front((*it));
249 }
250 }
251
252 // put transients of win's transients in the stack
253 for (it = win->getTransients().begin(); it != end; ++it) {
254 raiseTransients(*it, stack);
255 }
256 }
257
258
259 void Workspace::lowerTransients(const BlackboxWindow * const win,
260 StackVector::iterator &stack) {
261 if (win->getTransients().size() == 0) return; // nothing to do
262
263 // put transients of win's transients in the stack
264 BlackboxWindowList::const_reverse_iterator it,
265 end = win->getTransients().rend();
266 for (it = win->getTransients().rbegin(); it != end; ++it) {
267 lowerTransients(*it, stack);
268 }
269
270 // put win's transients in the stack
271 for (it = win->getTransients().rbegin(); it != end; ++it) {
272 *stack++ = (*it)->getFrameWindow();
273 screen->updateNetizenWindowLower((*it)->getClientWindow());
274
275 if (! (*it)->isIconic()) {
276 Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
277 wkspc->stackingList.remove((*it));
278 wkspc->stackingList.push_back((*it));
279 }
280 }
281 }
282
283
284 void Workspace::raiseWindow(BlackboxWindow *w) {
285 BlackboxWindow *win = w;
286
287 if (win->isDesktop()) return;
288
289 // walk up the transient_for's to the window that is not a transient
290 while (win->isTransient() && ! win->isDesktop()) {
291 if (! win->getTransientFor()) break;
292 win = win->getTransientFor();
293 }
294
295 // get the total window count (win and all transients)
296 unsigned int i = 1 + countTransients(win);
297
298 // stack the window with all transients above
299 StackVector stack_vector(i);
300 StackVector::iterator stack = stack_vector.begin();
301
302 *(stack++) = win->getFrameWindow();
303 screen->updateNetizenWindowRaise(win->getClientWindow());
304 if (! (win->isIconic() || win->isDesktop())) {
305 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
306 wkspc->stackingList.remove(win);
307 wkspc->stackingList.push_front(win);
308 }
309
310 raiseTransients(win, stack);
311
312 screen->raiseWindows(&stack_vector[0], stack_vector.size());
313 }
314
315
316 void Workspace::lowerWindow(BlackboxWindow *w) {
317 BlackboxWindow *win = w;
318
319 // walk up the transient_for's to the window that is not a transient
320 while (win->isTransient() && ! win->isDesktop()) {
321 if (! win->getTransientFor()) break;
322 win = win->getTransientFor();
323 }
324
325 // get the total window count (win and all transients)
326 unsigned int i = 1 + countTransients(win);
327
328 // stack the window with all transients above
329 StackVector stack_vector(i);
330 StackVector::iterator stack = stack_vector.begin();
331
332 lowerTransients(win, stack);
333
334 *(stack++) = win->getFrameWindow();
335 screen->updateNetizenWindowLower(win->getClientWindow());
336 if (! (win->isIconic() || win->isDesktop())) {
337 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
338 wkspc->stackingList.remove(win);
339 wkspc->stackingList.push_back(win);
340 }
341
342 screen->lowerWindows(&stack_vector[0], stack_vector.size());
343 }
344
345
346 void Workspace::reconfigure(void) {
347 clientmenu->reconfigure();
348 std::for_each(windowList.begin(), windowList.end(),
349 std::mem_fun(&BlackboxWindow::reconfigure));
350 }
351
352
353 BlackboxWindow *Workspace::getWindow(unsigned int index) {
354 if (index < windowList.size()) {
355 BlackboxWindowList::iterator it = windowList.begin();
356 for(; index > 0; --index, ++it); /* increment to index */
357 return *it;
358 }
359 return 0;
360 }
361
362
363 BlackboxWindow*
364 Workspace::getNextWindowInList(BlackboxWindow *w) {
365 BlackboxWindowList::iterator it = std::find(windowList.begin(),
366 windowList.end(),
367 w);
368 assert(it != windowList.end()); // window must be in list
369 ++it; // next window
370 if (it == windowList.end())
371 return windowList.front(); // if we walked off the end, wrap around
372
373 return *it;
374 }
375
376
377 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
378 BlackboxWindowList::iterator it = std::find(windowList.begin(),
379 windowList.end(),
380 w);
381 assert(it != windowList.end()); // window must be in list
382 if (it == windowList.begin())
383 return windowList.back(); // if we walked of the front, wrap around
384
385 return *(--it);
386 }
387
388
389 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
390 return stackingList.front();
391 }
392
393
394 void Workspace::sendWindowList(Netizen &n) {
395 BlackboxWindowList::iterator it = windowList.begin(),
396 end = windowList.end();
397 for(; it != end; ++it)
398 n.sendWindowAdd((*it)->getClientWindow(), getID());
399 }
400
401
402 unsigned int Workspace::getCount(void) const {
403 return windowList.size();
404 }
405
406
407 void Workspace::appendStackOrder(BlackboxWindowList &stack_order) const {
408 BlackboxWindowList::const_reverse_iterator it = stackingList.rbegin();
409 const BlackboxWindowList::const_reverse_iterator end = stackingList.rend();
410 for (; it != end; ++it)
411 stack_order.push_back(*it);
412 }
413
414
415 bool Workspace::isCurrent(void) const {
416 return (id == screen->getCurrentWorkspaceID());
417 }
418
419
420 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
421 return (w == windowList.back());
422 }
423
424
425 void Workspace::setCurrent(void) {
426 screen->changeWorkspaceID(id);
427 }
428
429
430 void Workspace::readName(void) {
431 XAtom::StringVect namesList;
432 unsigned long numnames = id + 1;
433
434 // attempt to get from the _NET_WM_DESKTOP_NAMES property
435 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
436 XAtom::utf8, numnames, namesList) &&
437 namesList.size() > id) {
438 name = namesList[id];
439
440 clientmenu->setLabel(name);
441 clientmenu->update();
442 } else {
443 /*
444 Use a default name. This doesn't actually change the class. That will
445 happen after the setName changes the root property, and that change
446 makes its way back to this function.
447 */
448 string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat,
449 "Workspace %d");
450 assert(tmp.length() < 32);
451 char default_name[32];
452 sprintf(default_name, tmp.c_str(), id + 1);
453
454 setName(default_name); // save this into the _NET_WM_DESKTOP_NAMES property
455 }
456 }
457
458
459 void Workspace::setName(const string& new_name) {
460 // set the _NET_WM_DESKTOP_NAMES property with the new name
461 XAtom::StringVect namesList;
462 unsigned long numnames = (unsigned) -1;
463 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
464 XAtom::utf8, numnames, namesList) &&
465 namesList.size() > id)
466 namesList[id] = new_name;
467 else
468 namesList.push_back(new_name);
469
470 xatom->setValue(screen->getRootWindow(), XAtom::net_desktop_names,
471 XAtom::utf8, namesList);
472 }
473
474
475 /*
476 * Calculate free space available for window placement.
477 */
478 typedef std::vector<Rect> rectList;
479
480 static rectList calcSpace(const Rect &win, const rectList &spaces) {
481 Rect isect, extra;
482 rectList result;
483 rectList::const_iterator siter, end = spaces.end();
484 for (siter = spaces.begin(); siter != end; ++siter) {
485 const Rect &curr = *siter;
486
487 if(! win.intersects(curr)) {
488 result.push_back(curr);
489 continue;
490 }
491
492 /* Use an intersection of win and curr to determine the space around
493 * curr that we can use.
494 *
495 * NOTE: the spaces calculated can overlap.
496 */
497 isect = curr & win;
498
499 // left
500 extra.setCoords(curr.left(), curr.top(),
501 isect.left() - 1, curr.bottom());
502 if (extra.valid()) result.push_back(extra);
503
504 // top
505 extra.setCoords(curr.left(), curr.top(),
506 curr.right(), isect.top() - 1);
507 if (extra.valid()) result.push_back(extra);
508
509 // right
510 extra.setCoords(isect.right() + 1, curr.top(),
511 curr.right(), curr.bottom());
512 if (extra.valid()) result.push_back(extra);
513
514 // bottom
515 extra.setCoords(curr.left(), isect.bottom() + 1,
516 curr.right(), curr.bottom());
517 if (extra.valid()) result.push_back(extra);
518 }
519 return result;
520 }
521
522
523 static bool rowRLBT(const Rect &first, const Rect &second) {
524 if (first.bottom() == second.bottom())
525 return first.right() > second.right();
526 return first.bottom() > second.bottom();
527 }
528
529 static bool rowRLTB(const Rect &first, const Rect &second) {
530 if (first.y() == second.y())
531 return first.right() > second.right();
532 return first.y() < second.y();
533 }
534
535 static bool rowLRBT(const Rect &first, const Rect &second) {
536 if (first.bottom() == second.bottom())
537 return first.x() < second.x();
538 return first.bottom() > second.bottom();
539 }
540
541 static bool rowLRTB(const Rect &first, const Rect &second) {
542 if (first.y() == second.y())
543 return first.x() < second.x();
544 return first.y() < second.y();
545 }
546
547 static bool colLRTB(const Rect &first, const Rect &second) {
548 if (first.x() == second.x())
549 return first.y() < second.y();
550 return first.x() < second.x();
551 }
552
553 static bool colLRBT(const Rect &first, const Rect &second) {
554 if (first.x() == second.x())
555 return first.bottom() > second.bottom();
556 return first.x() < second.x();
557 }
558
559 static bool colRLTB(const Rect &first, const Rect &second) {
560 if (first.right() == second.right())
561 return first.y() < second.y();
562 return first.right() > second.right();
563 }
564
565 static bool colRLBT(const Rect &first, const Rect &second) {
566 if (first.right() == second.right())
567 return first.bottom() > second.bottom();
568 return first.right() > second.right();
569 }
570
571
572 bool Workspace::smartPlacement(Rect& win, const Rect& availableArea) {
573 rectList spaces;
574 spaces.push_back(availableArea); //initially the entire screen is free
575
576 //Find Free Spaces
577 BlackboxWindowList::const_iterator wit = windowList.begin(),
578 end = windowList.end();
579 Rect tmp;
580 for (; wit != end; ++wit) {
581 const BlackboxWindow* const curr = *wit;
582
583 if (curr->isShaded() && screen->getPlaceIgnoreShaded()) continue;
584 if (curr->isMaximizedFull() && screen->getPlaceIgnoreMaximized()) continue;
585
586 tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
587 curr->frameRect().width() + screen->getBorderWidth(),
588 curr->frameRect().height() + screen->getBorderWidth());
589
590 spaces = calcSpace(tmp, spaces);
591 }
592
593 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
594 if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
595 if(screen->getColPlacementDirection() == BScreen::TopBottom)
596 std::sort(spaces.begin(), spaces.end(), rowLRTB);
597 else
598 std::sort(spaces.begin(), spaces.end(), rowLRBT);
599 } else {
600 if(screen->getColPlacementDirection() == BScreen::TopBottom)
601 std::sort(spaces.begin(), spaces.end(), rowRLTB);
602 else
603 std::sort(spaces.begin(), spaces.end(), rowRLBT);
604 }
605 } else {
606 if(screen->getColPlacementDirection() == BScreen::TopBottom) {
607 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
608 std::sort(spaces.begin(), spaces.end(), colLRTB);
609 else
610 std::sort(spaces.begin(), spaces.end(), colRLTB);
611 } else {
612 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
613 std::sort(spaces.begin(), spaces.end(), colLRBT);
614 else
615 std::sort(spaces.begin(), spaces.end(), colRLBT);
616 }
617 }
618
619 rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
620 for(; sit != spaces_end; ++sit) {
621 if (sit->width() >= win.width() && sit->height() >= win.height())
622 break;
623 }
624
625 if (sit == spaces_end)
626 return False;
627
628 //set new position based on the empty space found
629 const Rect& where = *sit;
630 win.setX(where.x());
631 win.setY(where.y());
632
633 // adjust the location() based on left/right and top/bottom placement
634 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
635 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
636 win.setX(where.right() - win.width());
637 if (screen->getColPlacementDirection() == BScreen::BottomTop)
638 win.setY(where.bottom() - win.height());
639 } else {
640 if (screen->getColPlacementDirection() == BScreen::BottomTop)
641 win.setY(win.y() + where.height() - win.height());
642 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
643 win.setX(win.x() + where.width() - win.width());
644 }
645 return True;
646 }
647
648
649 bool Workspace::underMousePlacement(Rect &win, const Rect &availableArea) {
650 int x, y, rx, ry;
651 Window c, r;
652 unsigned int m;
653 XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
654 &r, &c, &rx, &ry, &x, &y, &m);
655 x = rx - win.width() / 2;
656 y = ry - win.height() / 2;
657
658 if (x < availableArea.x())
659 x = availableArea.x();
660 if (y < availableArea.y())
661 y = availableArea.y();
662 if (x + win.width() > availableArea.x() + availableArea.width())
663 x = availableArea.x() + availableArea.width() - win.width();
664 if (y + win.height() > availableArea.y() + availableArea.height())
665 y = availableArea.y() + availableArea.height() - win.height();
666
667 win.setX(x);
668 win.setY(y);
669
670 return True;
671 }
672
673
674 bool Workspace::cascadePlacement(Rect &win, const Rect &availableArea) {
675 if ((cascade_x > static_cast<signed>(availableArea.width() / 2)) ||
676 (cascade_y > static_cast<signed>(availableArea.height() / 2)))
677 cascade_x = cascade_y = 32;
678
679 if (cascade_x == 32) {
680 cascade_x += availableArea.x();
681 cascade_y += availableArea.y();
682 }
683
684 win.setPos(cascade_x, cascade_y);
685
686 return True;
687 }
688
689
690 void Workspace::placeWindow(BlackboxWindow *win) {
691 Rect availableArea(screen->availableArea()),
692 new_win(availableArea.x(), availableArea.y(),
693 win->frameRect().width(), win->frameRect().height());
694 bool placed = False;
695
696 switch (screen->getPlacementPolicy()) {
697 case BScreen::RowSmartPlacement:
698 case BScreen::ColSmartPlacement:
699 placed = smartPlacement(new_win, availableArea);
700 break;
701 case BScreen::UnderMousePlacement:
702 case BScreen::ClickMousePlacement:
703 placed = underMousePlacement(new_win, availableArea);
704 default:
705 break; // handled below
706 } // switch
707
708 if (placed == False) {
709 cascadePlacement(new_win, availableArea);
710 cascade_x += win->getTitleHeight() + (screen->getBorderWidth() * 2);
711 cascade_y += win->getTitleHeight() + (screen->getBorderWidth() * 2);
712 }
713
714 if (new_win.right() > availableArea.right())
715 new_win.setX(availableArea.left());
716 if (new_win.bottom() > availableArea.bottom())
717 new_win.setY(availableArea.top());
718 win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());
719 }
This page took 0.078298 seconds and 5 git commands to generate.