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)
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:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
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.
25 # include "../config.h"
26 #endif // HAVE_CONFIG_H
30 #include <X11/Xatom.h>
34 #endif // HAVE_STDIO_H
38 #endif // HAVE_STRING_H
49 #include "blackbox.hh"
50 #include "Clientmenu.hh"
57 #include "Workspace.hh"
58 #include "Windowmenu.hh"
62 Workspace::Workspace(BScreen *scrn, unsigned int i) {
64 xatom = screen->getBlackbox()->getXAtom();
66 cascade_x = cascade_y = 0;
73 clientmenu = new Clientmenu(this);
75 lastfocus = (BlackboxWindow *) 0;
81 void Workspace::addWindow(BlackboxWindow *w, bool place, bool sticky) {
84 if (place) placeWindow(w);
86 stackingList.push_front(w);
91 w->setWindowNumber(windowList.size());
94 windowList.push_back(w);
96 clientmenu->insert(w->getTitle());
100 screen->updateNetizenWindowAdd(w->getClientWindow(), id);
102 if (screen->doFocusNew() || (w->isTransient() && w->getTransientFor() &&
103 w->getTransientFor()->isFocused())) {
104 if (id == screen->getCurrentWorkspaceID())
108 not on the focused workspace, so the window is not going to get focus
109 but if the user wants new windows focused, then it should get focus
110 when this workspace does become focused.
117 if (! w->isDesktop())
122 // if the window is sticky, then it needs to be added on all other
124 if (! sticky && w->isStuck()) {
125 for (unsigned int i = 0; i < screen->getWorkspaceCount(); ++i)
127 screen->getWorkspace(i)->addWindow(w, place, True);
132 void Workspace::removeWindow(BlackboxWindow *w, bool sticky) {
135 stackingList.remove(w);
137 // pass focus to the next appropriate window
138 if ((w->isFocused() || w == lastfocus) &&
139 ! screen->getBlackbox()->doShutdown()) {
143 // if the window is sticky, then it needs to be removed on all other
145 if (! sticky && w->isStuck()) {
146 for (unsigned int i = 0; i < screen->getWorkspaceCount(); ++i)
148 screen->getWorkspace(i)->removeWindow(w, True);
151 if (! w->isNormal()) return;
153 BlackboxWindowList::iterator it, end = windowList.end();
155 for (i = 0, it = windowList.begin(); it != end; ++it, ++i)
160 windowList.erase(it);
161 clientmenu->remove(i);
162 clientmenu->update();
165 screen->updateNetizenWindowDel(w->getClientWindow());
167 BlackboxWindowList::iterator it = windowList.begin();
168 const BlackboxWindowList::iterator end = windowList.end();
170 for (; it != end; ++it, ++i)
171 (*it)->setWindowNumber(i);
175 cascade_x = cascade_y = 0;
183 void Workspace::focusFallback(const BlackboxWindow *old_window) {
184 BlackboxWindow *newfocus = 0;
186 if (id == screen->getCurrentWorkspaceID()) {
187 // The window is on the visible workspace.
189 // if it's a transient, then try to focus its parent
190 if (old_window && old_window->isTransient()) {
191 newfocus = old_window->getTransientFor();
194 newfocus->isIconic() || // do not focus icons
195 newfocus->getWorkspaceNumber() != id || // or other workspaces
196 ! newfocus->setInputFocus())
201 BlackboxWindowList::iterator it = stackingList.begin(),
202 end = stackingList.end();
203 for (; it != end; ++it) {
204 BlackboxWindow *tmp = *it;
205 if (tmp && tmp->isNormal() && tmp->setInputFocus()) {
206 // we found our new focus target
213 screen->getBlackbox()->setFocusedWindow(newfocus);
215 // The window is not on the visible workspace.
217 if (old_window && lastfocus == old_window) {
218 // The window was the last-focus target, so we need to replace it.
219 BlackboxWindow *win = (BlackboxWindow*) 0;
220 if (! stackingList.empty())
221 win = stackingList.front();
222 setLastFocusedWindow(win);
228 void Workspace::setFocused(const BlackboxWindow *w, bool focused) {
229 BlackboxWindowList::iterator it, end = windowList.end();
231 for (i = 0, it = windowList.begin(); it != end; ++it, ++i)
234 // if its == end, then a window thats not in the windowList
235 // got focused, such as a !isNormal() window.
237 clientmenu->setItemSelected(i, focused);
241 void Workspace::showAll(void) {
242 BlackboxWindowList::iterator it = stackingList.begin();
243 const BlackboxWindowList::iterator end = stackingList.end();
244 for (; it != end; ++it) {
245 BlackboxWindow *bw = *it;
251 void Workspace::hideAll(void) {
252 // withdraw in reverse order to minimize the number of Expose events
253 BlackboxWindowList::reverse_iterator it = stackingList.rbegin();
254 const BlackboxWindowList::reverse_iterator end = stackingList.rend();
256 BlackboxWindow *bw = *it;
257 ++it; // withdraw removes the current item from the list so we need the next
258 // iterator before that happens
264 void Workspace::removeAll(void) {
265 while (! windowList.empty())
266 windowList.front()->iconify();
271 * returns the number of transients for win, plus the number of transients
272 * associated with each transient of win
274 static int countTransients(const BlackboxWindow * const win) {
275 int ret = win->getTransients().size();
277 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
278 for (it = win->getTransients().begin(); it != end; ++it) {
279 ret += countTransients(*it);
287 * puts the transients of win into the stack. windows are stacked above
288 * the window before it in the stackvector being iterated, meaning
289 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
292 void Workspace::raiseTransients(const BlackboxWindow * const win,
293 StackVector::iterator &stack) {
294 if (win->getTransients().size() == 0) return; // nothing to do
296 // put win's transients in the stack
297 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
298 for (it = win->getTransients().begin(); it != end; ++it) {
299 *stack++ = (*it)->getFrameWindow();
300 screen->updateNetizenWindowRaise((*it)->getClientWindow());
302 if (! (*it)->isIconic()) {
303 Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
304 wkspc->stackingList.remove((*it));
305 wkspc->stackingList.push_front((*it));
309 // put transients of win's transients in the stack
310 for (it = win->getTransients().begin(); it != end; ++it) {
311 raiseTransients(*it, stack);
316 void Workspace::lowerTransients(const BlackboxWindow * const win,
317 StackVector::iterator &stack) {
318 if (win->getTransients().size() == 0) return; // nothing to do
320 // put transients of win's transients in the stack
321 BlackboxWindowList::const_reverse_iterator it,
322 end = win->getTransients().rend();
323 for (it = win->getTransients().rbegin(); it != end; ++it) {
324 lowerTransients(*it, stack);
327 // put win's transients in the stack
328 for (it = win->getTransients().rbegin(); it != end; ++it) {
329 *stack++ = (*it)->getFrameWindow();
330 screen->updateNetizenWindowLower((*it)->getClientWindow());
332 if (! (*it)->isIconic()) {
333 Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
334 wkspc->stackingList.remove((*it));
335 wkspc->stackingList.push_back((*it));
341 void Workspace::raiseWindow(BlackboxWindow *w) {
342 BlackboxWindow *win = w;
344 if (win->isDesktop()) return;
346 // walk up the transient_for's to the window that is not a transient
347 while (win->isTransient() && ! win->isDesktop()) {
348 if (! win->getTransientFor()) break;
349 win = win->getTransientFor();
352 // get the total window count (win and all transients)
353 unsigned int i = 1 + countTransients(win);
355 // stack the window with all transients above
356 StackVector stack_vector(i);
357 StackVector::iterator stack = stack_vector.begin();
359 *(stack++) = win->getFrameWindow();
360 screen->updateNetizenWindowRaise(win->getClientWindow());
361 if (! (win->isIconic() || win->isDesktop())) {
362 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
363 wkspc->stackingList.remove(win);
364 wkspc->stackingList.push_front(win);
367 raiseTransients(win, stack);
369 screen->raiseWindows(&stack_vector[0], stack_vector.size());
373 void Workspace::lowerWindow(BlackboxWindow *w) {
374 BlackboxWindow *win = w;
376 // walk up the transient_for's to the window that is not a transient
377 while (win->isTransient() && ! win->isDesktop()) {
378 if (! win->getTransientFor()) break;
379 win = win->getTransientFor();
382 // get the total window count (win and all transients)
383 unsigned int i = 1 + countTransients(win);
385 // stack the window with all transients above
386 StackVector stack_vector(i);
387 StackVector::iterator stack = stack_vector.begin();
389 lowerTransients(win, stack);
391 *(stack++) = win->getFrameWindow();
392 screen->updateNetizenWindowLower(win->getClientWindow());
393 if (! (win->isIconic() || win->isDesktop())) {
394 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
395 wkspc->stackingList.remove(win);
396 wkspc->stackingList.push_back(win);
399 screen->lowerWindows(&stack_vector[0], stack_vector.size());
403 void Workspace::reconfigure(void) {
404 clientmenu->reconfigure();
405 std::for_each(windowList.begin(), windowList.end(),
406 std::mem_fun(&BlackboxWindow::reconfigure));
410 BlackboxWindow *Workspace::getWindow(unsigned int index) {
411 if (index < windowList.size()) {
412 BlackboxWindowList::iterator it = windowList.begin();
413 for(; index > 0; --index, ++it); /* increment to index */
421 Workspace::getNextWindowInList(BlackboxWindow *w) {
422 BlackboxWindowList::iterator it = std::find(windowList.begin(),
425 assert(it != windowList.end()); // window must be in list
427 if (it == windowList.end())
428 return windowList.front(); // if we walked off the end, wrap around
434 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
435 BlackboxWindowList::iterator it = std::find(windowList.begin(),
438 assert(it != windowList.end()); // window must be in list
439 if (it == windowList.begin())
440 return windowList.back(); // if we walked of the front, wrap around
446 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
447 return stackingList.front();
451 void Workspace::sendWindowList(Netizen &n) {
452 BlackboxWindowList::iterator it = windowList.begin(),
453 end = windowList.end();
454 for(; it != end; ++it)
455 n.sendWindowAdd((*it)->getClientWindow(), getID());
459 unsigned int Workspace::getCount(void) const {
460 return windowList.size();
464 void Workspace::appendStackOrder(BlackboxWindowList &stack_order) const {
465 BlackboxWindowList::const_reverse_iterator it = stackingList.rbegin();
466 const BlackboxWindowList::const_reverse_iterator end = stackingList.rend();
467 for (; it != end; ++it)
468 if ((*it)->isNormal())
469 stack_order.push_back(*it);
473 bool Workspace::isCurrent(void) const {
474 return (id == screen->getCurrentWorkspaceID());
478 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
479 return (w == windowList.back());
483 void Workspace::setCurrent(void) {
484 screen->changeWorkspaceID(id);
488 void Workspace::readName(void) {
489 XAtom::StringVect namesList;
490 unsigned long numnames = id + 1;
492 // attempt to get from the _NET_WM_DESKTOP_NAMES property
493 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
494 XAtom::utf8, numnames, namesList) &&
495 namesList.size() > id) {
496 name = namesList[id];
498 clientmenu->setLabel(name);
499 clientmenu->update();
502 Use a default name. This doesn't actually change the class. That will
503 happen after the setName changes the root property, and that change
504 makes its way back to this function.
506 string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat,
508 assert(tmp.length() < 32);
509 char default_name[32];
510 sprintf(default_name, tmp.c_str(), id + 1);
512 setName(default_name); // save this into the _NET_WM_DESKTOP_NAMES property
517 void Workspace::setName(const string& new_name) {
518 // set the _NET_WM_DESKTOP_NAMES property with the new name
519 XAtom::StringVect namesList;
520 unsigned long numnames = (unsigned) -1;
521 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
522 XAtom::utf8, numnames, namesList) &&
523 namesList.size() > id)
524 namesList[id] = new_name;
526 namesList.push_back(new_name);
528 xatom->setValue(screen->getRootWindow(), XAtom::net_desktop_names,
529 XAtom::utf8, namesList);
534 * Calculate free space available for window placement.
536 typedef std::vector<Rect> rectList;
538 static rectList calcSpace(const Rect &win, const rectList &spaces) {
541 rectList::const_iterator siter, end = spaces.end();
542 for (siter = spaces.begin(); siter != end; ++siter) {
543 const Rect &curr = *siter;
545 if(! win.intersects(curr)) {
546 result.push_back(curr);
550 /* Use an intersection of win and curr to determine the space around
551 * curr that we can use.
553 * NOTE: the spaces calculated can overlap.
558 extra.setCoords(curr.left(), curr.top(),
559 isect.left() - 1, curr.bottom());
560 if (extra.valid()) result.push_back(extra);
563 extra.setCoords(curr.left(), curr.top(),
564 curr.right(), isect.top() - 1);
565 if (extra.valid()) result.push_back(extra);
568 extra.setCoords(isect.right() + 1, curr.top(),
569 curr.right(), curr.bottom());
570 if (extra.valid()) result.push_back(extra);
573 extra.setCoords(curr.left(), isect.bottom() + 1,
574 curr.right(), curr.bottom());
575 if (extra.valid()) result.push_back(extra);
581 static bool rowRLBT(const Rect &first, const Rect &second) {
582 if (first.bottom() == second.bottom())
583 return first.right() > second.right();
584 return first.bottom() > second.bottom();
587 static bool rowRLTB(const Rect &first, const Rect &second) {
588 if (first.y() == second.y())
589 return first.right() > second.right();
590 return first.y() < second.y();
593 static bool rowLRBT(const Rect &first, const Rect &second) {
594 if (first.bottom() == second.bottom())
595 return first.x() < second.x();
596 return first.bottom() > second.bottom();
599 static bool rowLRTB(const Rect &first, const Rect &second) {
600 if (first.y() == second.y())
601 return first.x() < second.x();
602 return first.y() < second.y();
605 static bool colLRTB(const Rect &first, const Rect &second) {
606 if (first.x() == second.x())
607 return first.y() < second.y();
608 return first.x() < second.x();
611 static bool colLRBT(const Rect &first, const Rect &second) {
612 if (first.x() == second.x())
613 return first.bottom() > second.bottom();
614 return first.x() < second.x();
617 static bool colRLTB(const Rect &first, const Rect &second) {
618 if (first.right() == second.right())
619 return first.y() < second.y();
620 return first.right() > second.right();
623 static bool colRLBT(const Rect &first, const Rect &second) {
624 if (first.right() == second.right())
625 return first.bottom() > second.bottom();
626 return first.right() > second.right();
630 bool Workspace::smartPlacement(Rect& win) {
633 //initially the entire screen is free
635 if (screen->isXineramaActive() &&
636 screen->getBlackbox()->doXineramaPlacement()) {
637 RectList availableAreas = screen->allAvailableAreas();
638 RectList::iterator it, end = availableAreas.end();
640 for (it = availableAreas.begin(); it != end; ++it)
641 spaces.push_back(*it);
644 spaces.push_back(screen->availableArea());
647 BlackboxWindowList::const_iterator wit = windowList.begin(),
648 end = windowList.end();
650 for (; wit != end; ++wit) {
651 const BlackboxWindow* const curr = *wit;
653 // watch for shaded windows and full-maxed windows
654 if (curr->isShaded()) {
655 if (screen->getPlaceIgnoreShaded()) continue;
656 } else if (curr->isMaximizedFull()) {
657 if (screen->getPlaceIgnoreMaximized()) continue;
660 tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
661 curr->frameRect().width() + screen->getBorderWidth(),
662 curr->frameRect().height() + screen->getBorderWidth());
664 spaces = calcSpace(tmp, spaces);
667 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
668 if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
669 if(screen->getColPlacementDirection() == BScreen::TopBottom)
670 std::sort(spaces.begin(), spaces.end(), rowLRTB);
672 std::sort(spaces.begin(), spaces.end(), rowLRBT);
674 if(screen->getColPlacementDirection() == BScreen::TopBottom)
675 std::sort(spaces.begin(), spaces.end(), rowRLTB);
677 std::sort(spaces.begin(), spaces.end(), rowRLBT);
680 if(screen->getColPlacementDirection() == BScreen::TopBottom) {
681 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
682 std::sort(spaces.begin(), spaces.end(), colLRTB);
684 std::sort(spaces.begin(), spaces.end(), colRLTB);
686 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
687 std::sort(spaces.begin(), spaces.end(), colLRBT);
689 std::sort(spaces.begin(), spaces.end(), colRLBT);
693 rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
694 for(; sit != spaces_end; ++sit) {
695 if (sit->width() >= win.width() && sit->height() >= win.height())
699 if (sit == spaces_end)
702 //set new position based on the empty space found
703 const Rect& where = *sit;
707 // adjust the location() based on left/right and top/bottom placement
708 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
709 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
710 win.setX(where.right() - win.width());
711 if (screen->getColPlacementDirection() == BScreen::BottomTop)
712 win.setY(where.bottom() - win.height());
714 if (screen->getColPlacementDirection() == BScreen::BottomTop)
715 win.setY(win.y() + where.height() - win.height());
716 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
717 win.setX(win.x() + where.width() - win.width());
723 bool Workspace::underMousePlacement(Rect &win) {
727 XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
728 &r, &c, &rx, &ry, &x, &y, &m);
732 if (screen->isXineramaActive() &&
733 screen->getBlackbox()->doXineramaPlacement()) {
734 RectList availableAreas = screen->allAvailableAreas();
735 RectList::iterator it, end = availableAreas.end();
737 for (it = availableAreas.begin(); it != end; ++it)
738 if (it->contains(rx, ry)) break;
739 assert(it != end); // the mouse isn't inside an area?
743 area = screen->availableArea();
745 x = rx - win.width() / 2;
746 y = ry - win.height() / 2;
752 if (x + win.width() > area.x() + area.width())
753 x = area.x() + area.width() - win.width();
754 if (y + win.height() > area.y() + area.height())
755 y = area.y() + area.height() - win.height();
764 bool Workspace::cascadePlacement(Rect &win, const int offset) {
768 if (screen->isXineramaActive() &&
769 screen->getBlackbox()->doXineramaPlacement()) {
770 area = screen->allAvailableAreas()[cascade_region];
773 area = screen->availableArea();
775 if ((static_cast<signed>(cascade_x + win.width()) > area.right() + 1) ||
776 (static_cast<signed>(cascade_y + win.height()) > area.bottom() + 1)) {
777 cascade_x = cascade_y = 0;
779 if (screen->isXineramaActive() &&
780 screen->getBlackbox()->doXineramaPlacement()) {
781 // go to the next xinerama region, and use its area
782 if (++cascade_region >= screen->allAvailableAreas().size())
784 area = screen->allAvailableAreas()[cascade_region];
789 if (cascade_x == 0) {
790 cascade_x = area.x() + offset;
791 cascade_y = area.y() + offset;
794 win.setPos(cascade_x, cascade_y);
803 void Workspace::placeWindow(BlackboxWindow *win) {
804 Rect new_win(0, 0, win->frameRect().width(), win->frameRect().height());
807 switch (screen->getPlacementPolicy()) {
808 case BScreen::RowSmartPlacement:
809 case BScreen::ColSmartPlacement:
810 placed = smartPlacement(new_win);
812 case BScreen::UnderMousePlacement:
813 case BScreen::ClickMousePlacement:
814 placed = underMousePlacement(new_win);
816 break; // handled below
820 cascadePlacement(new_win, (win->getTitleHeight() +
821 screen->getBorderWidth() * 2));
823 if (new_win.right() > screen->availableArea().right())
824 new_win.setX(screen->availableArea().left());
825 if (new_win.bottom() > screen->availableArea().bottom())
826 new_win.setY(screen->availableArea().top());
828 win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());