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
48 #include "blackbox.hh"
53 #include "workspace.hh"
57 Workspace::Workspace(BScreen *scrn, unsigned int i) {
59 xatom = screen->getBlackbox()->getXAtom();
61 cascade_x = cascade_y = 0;
68 lastfocus = (BlackboxWindow *) 0;
74 void Workspace::addWindow(BlackboxWindow *w, bool place, bool sticky) {
77 if (place) placeWindow(w);
79 stackingList.push_front(w);
84 if (! w->isNormal()) {
86 // just give it some number, else bad things happen as it is assumed to
87 // not be on a workspace
88 w->setWindowNumber(0);
92 w->setWindowNumber(windowList.size());
94 windowList.push_back(w);
96 if (screen->doFocusNew() || (w->isTransient() && w->getTransientFor() &&
97 w->getTransientFor()->isFocused())) {
98 if (id != screen->getCurrentWorkspaceID()) {
100 not on the focused workspace, so the window is not going to get focus
101 but if the user wants new windows focused, then it should get focus
102 when this workspace does become focused.
109 if (! w->isDesktop())
116 void Workspace::removeWindow(BlackboxWindow *w, bool sticky) {
119 stackingList.remove(w);
121 // pass focus to the next appropriate window
122 if ((w->isFocused() || w == lastfocus) &&
123 ! screen->getBlackbox()->doShutdown()) {
127 if (! w->isNormal()) return;
129 BlackboxWindowList::iterator it, end = windowList.end();
131 for (i = 0, it = windowList.begin(); it != end; ++it, ++i)
136 windowList.erase(it);
138 BlackboxWindowList::iterator it = windowList.begin();
139 const BlackboxWindowList::iterator end = windowList.end();
141 for (; it != end; ++it, ++i)
142 (*it)->setWindowNumber(i);
146 cascade_x = cascade_y = 0;
154 void Workspace::focusFallback(const BlackboxWindow *old_window) {
155 BlackboxWindow *newfocus = 0;
157 if (id == screen->getCurrentWorkspaceID()) {
158 // The window is on the visible workspace.
160 // if it's a transient, then try to focus its parent
161 if (old_window && old_window->isTransient()) {
162 newfocus = old_window->getTransientFor();
165 newfocus->isIconic() || // do not focus icons
166 newfocus->getWorkspaceNumber() != id || // or other workspaces
167 ! newfocus->setInputFocus())
172 BlackboxWindowList::iterator it = stackingList.begin(),
173 end = stackingList.end();
174 for (; it != end; ++it) {
175 BlackboxWindow *tmp = *it;
176 if (tmp && tmp->isNormal() && tmp->setInputFocus()) {
177 // we found our new focus target
184 screen->getBlackbox()->setFocusedWindow(newfocus);
186 // The window is not on the visible workspace.
188 if (old_window && lastfocus == old_window) {
189 // The window was the last-focus target, so we need to replace it.
190 BlackboxWindow *win = (BlackboxWindow*) 0;
191 if (! stackingList.empty())
192 win = stackingList.front();
193 setLastFocusedWindow(win);
199 void Workspace::removeAll(void) {
200 while (! windowList.empty())
201 windowList.front()->iconify();
204 void Workspace::showAll(void) {
205 BlackboxWindowList::iterator it = stackingList.begin();
206 const BlackboxWindowList::iterator end = stackingList.end();
207 for (; it != end; ++it) {
208 BlackboxWindow *bw = *it;
209 // sticky windows arent unmapped on a workspace change so we don't have ot
210 // map them, but sometimes on a restart, another app can unmap our sticky
211 // windows, so we map on startup always
212 if (! bw->isStuck() || screen->getBlackbox()->isStartup())
218 void Workspace::hideAll(void) {
219 // withdraw in reverse order to minimize the number of Expose events
221 BlackboxWindowList lst(stackingList.rbegin(), stackingList.rend());
223 BlackboxWindowList::iterator it = lst.begin();
224 const BlackboxWindowList::iterator end = lst.end();
225 for (; it != end; ++it) {
226 BlackboxWindow *bw = *it;
227 // don't hide sticky windows, or they'll end up flickering on a workspace
237 * returns the number of transients for win, plus the number of transients
238 * associated with each transient of win
240 static unsigned int countTransients(const BlackboxWindow * const win) {
241 BlackboxWindowList transients = win->getTransients();
242 if (transients.empty()) return 0;
244 unsigned int ret = transients.size();
245 BlackboxWindowList::const_iterator it = transients.begin(),
246 end = transients.end();
247 for (; it != end; ++it)
248 ret += countTransients(*it);
255 * puts the transients of win into the stack. windows are stacked above
256 * the window before it in the stackvector being iterated, meaning
257 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
260 void Workspace::raiseTransients(const BlackboxWindow * const win,
261 StackVector::iterator &stack) {
262 if (win->getTransients().empty()) return; // nothing to do
264 // put win's transients in the stack
265 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
266 for (it = win->getTransients().begin(); it != end; ++it) {
267 BlackboxWindow *w = *it;
268 *stack++ = w->getFrameWindow();
270 if (! w->isIconic()) {
271 Workspace *wkspc = screen->getWorkspace(w->getWorkspaceNumber());
272 wkspc->stackingList.remove(w);
273 wkspc->stackingList.push_front(w);
277 // put transients of win's transients in the stack
278 for (it = win->getTransients().begin(); it != end; ++it)
279 raiseTransients(*it, stack);
283 void Workspace::lowerTransients(const BlackboxWindow * const win,
284 StackVector::iterator &stack) {
285 if (win->getTransients().empty()) return; // nothing to do
287 // put transients of win's transients in the stack
288 BlackboxWindowList::const_reverse_iterator it,
289 end = win->getTransients().rend();
290 for (it = win->getTransients().rbegin(); it != end; ++it)
291 lowerTransients(*it, stack);
293 // put win's transients in the stack
294 for (it = win->getTransients().rbegin(); it != end; ++it) {
295 BlackboxWindow *w = *it;
296 *stack++ = w->getFrameWindow();
298 if (! w->isIconic()) {
299 Workspace *wkspc = screen->getWorkspace(w->getWorkspaceNumber());
300 wkspc->stackingList.remove(w);
301 wkspc->stackingList.push_back(w);
307 void Workspace::raiseWindow(BlackboxWindow *w) {
308 BlackboxWindow *win = w;
310 if (win->isDesktop()) return;
312 // walk up the transient_for's to the window that is not a transient
313 while (win->isTransient() && win->getTransientFor())
314 win = win->getTransientFor();
316 // get the total window count (win and all transients)
317 unsigned int i = 1 + countTransients(win);
319 // stack the window with all transients above
320 StackVector stack_vector(i);
321 StackVector::iterator stack = stack_vector.begin();
323 *(stack++) = win->getFrameWindow();
324 if (! (win->isIconic() || win->isDesktop())) {
325 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
326 wkspc->stackingList.remove(win);
327 wkspc->stackingList.push_front(win);
330 raiseTransients(win, stack);
332 screen->raiseWindows(&stack_vector[0], stack_vector.size());
336 void Workspace::lowerWindow(BlackboxWindow *w) {
337 BlackboxWindow *win = w;
339 // walk up the transient_for's to the window that is not a transient
340 while (win->isTransient() && win->getTransientFor())
341 win = win->getTransientFor();
343 // get the total window count (win and all transients)
344 unsigned int i = 1 + countTransients(win);
346 // stack the window with all transients above
347 StackVector stack_vector(i);
348 StackVector::iterator stack = stack_vector.begin();
350 lowerTransients(win, stack);
352 *(stack++) = win->getFrameWindow();
353 if (! (win->isIconic() || win->isDesktop())) {
354 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
355 wkspc->stackingList.remove(win);
356 wkspc->stackingList.push_back(win);
359 screen->lowerWindows(&stack_vector[0], stack_vector.size());
363 void Workspace::reconfigure(void) {
364 std::for_each(windowList.begin(), windowList.end(),
365 std::mem_fun(&BlackboxWindow::reconfigure));
369 BlackboxWindow *Workspace::getWindow(unsigned int index) {
370 if (index < windowList.size()) {
371 BlackboxWindowList::iterator it = windowList.begin();
372 while (index-- > 0) // increment to index
382 Workspace::getNextWindowInList(BlackboxWindow *w) {
383 BlackboxWindowList::iterator it = std::find(windowList.begin(),
386 assert(it != windowList.end()); // window must be in list
388 if (it == windowList.end())
389 return windowList.front(); // if we walked off the end, wrap around
395 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
396 BlackboxWindowList::iterator it = std::find(windowList.begin(),
399 assert(it != windowList.end()); // window must be in list
400 if (it == windowList.begin())
401 return windowList.back(); // if we walked of the front, wrap around
407 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
408 assert(! stackingList.empty());
409 return stackingList.front();
413 unsigned int Workspace::getCount(void) const {
414 return windowList.size();
418 void Workspace::appendStackOrder(BlackboxWindowList &stack_order) const {
419 BlackboxWindowList::const_reverse_iterator it = stackingList.rbegin();
420 const BlackboxWindowList::const_reverse_iterator end = stackingList.rend();
421 for (; it != end; ++it)
422 // don't add desktop wnidows, or sticky windows more than once
423 if (! ( (*it)->isDesktop() ||
424 ((*it)->isStuck() && id != screen->getCurrentWorkspaceID())))
425 stack_order.push_back(*it);
429 bool Workspace::isCurrent(void) const {
430 return (id == screen->getCurrentWorkspaceID());
434 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
435 return (w == windowList.back());
439 void Workspace::setCurrent(void) {
440 screen->changeWorkspaceID(id);
444 void Workspace::readName(void) {
445 XAtom::StringVect namesList;
446 unsigned long numnames = id + 1;
448 // attempt to get from the _NET_WM_DESKTOP_NAMES property
449 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
450 XAtom::utf8, numnames, namesList) &&
451 namesList.size() > id) {
452 name = namesList[id];
456 Use a default name. This doesn't actually change the class. That will
457 happen after the setName changes the root property, and that change
458 makes its way back to this function.
460 string tmp = "Workspace %d";
461 assert(tmp.length() < 32);
462 char default_name[32];
463 sprintf(default_name, tmp.c_str(), id + 1);
465 setName(default_name); // save this into the _NET_WM_DESKTOP_NAMES property
470 void Workspace::setName(const string& new_name) {
471 // set the _NET_WM_DESKTOP_NAMES property with the new name
472 XAtom::StringVect namesList;
473 unsigned long numnames = (unsigned) -1;
474 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
475 XAtom::utf8, numnames, namesList) &&
476 namesList.size() > id)
477 namesList[id] = new_name;
479 namesList.push_back(new_name);
481 xatom->setValue(screen->getRootWindow(), XAtom::net_desktop_names,
482 XAtom::utf8, namesList);
487 * Calculate free space available for window placement.
489 Workspace::rectList Workspace::calcSpace(const Rect &win,
490 const rectList &spaces) const {
493 rectList::const_iterator siter, end = spaces.end();
494 for (siter = spaces.begin(); siter != end; ++siter) {
495 const Rect &curr = *siter;
497 if(! win.intersects(curr)) {
498 result.push_back(curr);
502 /* Use an intersection of win and curr to determine the space around
503 * curr that we can use.
505 * NOTE: the spaces calculated can overlap.
510 extra.setCoords(curr.left(), curr.top(),
511 isect.left() - screen->getSnapOffset(), curr.bottom());
512 if (extra.valid()) result.push_back(extra);
515 extra.setCoords(curr.left(), curr.top(),
516 curr.right(), isect.top() - screen->getSnapOffset());
517 if (extra.valid()) result.push_back(extra);
520 extra.setCoords(isect.right() + screen->getSnapOffset(), curr.top(),
521 curr.right(), curr.bottom());
522 if (extra.valid()) result.push_back(extra);
525 extra.setCoords(curr.left(), isect.bottom() + screen->getSnapOffset(),
526 curr.right(), curr.bottom());
527 if (extra.valid()) result.push_back(extra);
533 static bool rowRLBT(const Rect &first, const Rect &second) {
534 if (first.bottom() == second.bottom())
535 return first.right() > second.right();
536 return first.bottom() > second.bottom();
539 static bool rowRLTB(const Rect &first, const Rect &second) {
540 if (first.y() == second.y())
541 return first.right() > second.right();
542 return first.y() < second.y();
545 static bool rowLRBT(const Rect &first, const Rect &second) {
546 if (first.bottom() == second.bottom())
547 return first.x() < second.x();
548 return first.bottom() > second.bottom();
551 static bool rowLRTB(const Rect &first, const Rect &second) {
552 if (first.y() == second.y())
553 return first.x() < second.x();
554 return first.y() < second.y();
557 static bool colLRTB(const Rect &first, const Rect &second) {
558 if (first.x() == second.x())
559 return first.y() < second.y();
560 return first.x() < second.x();
563 static bool colLRBT(const Rect &first, const Rect &second) {
564 if (first.x() == second.x())
565 return first.bottom() > second.bottom();
566 return first.x() < second.x();
569 static bool colRLTB(const Rect &first, const Rect &second) {
570 if (first.right() == second.right())
571 return first.y() < second.y();
572 return first.right() > second.right();
575 static bool colRLBT(const Rect &first, const Rect &second) {
576 if (first.right() == second.right())
577 return first.bottom() > second.bottom();
578 return first.right() > second.right();
582 bool Workspace::smartPlacement(Rect& win) {
585 //initially the entire screen is free
587 if (screen->isXineramaActive() &&
588 screen->getBlackbox()->doXineramaPlacement()) {
589 RectList availableAreas = screen->allAvailableAreas();
590 RectList::iterator it, end = availableAreas.end();
592 for (it = availableAreas.begin(); it != end; ++it) {
594 r.setRect(r.x() + screen->getSnapOffset(),
595 r.y() + screen->getSnapOffset(),
596 r.width() - screen->getSnapOffset(),
597 r.height() - screen->getSnapOffset());
598 spaces.push_back(*it);
603 Rect r = screen->availableArea();
604 r.setRect(r.x() + screen->getSnapOffset(),
605 r.y() + screen->getSnapOffset(),
606 r.width() - screen->getSnapOffset(),
607 r.height() - screen->getSnapOffset());
612 BlackboxWindowList::const_iterator wit = windowList.begin(),
613 end = windowList.end();
615 for (; wit != end; ++wit) {
616 const BlackboxWindow* const curr = *wit;
618 // watch for shaded windows and full-maxed windows
619 if (curr->isShaded()) {
620 if (screen->getPlaceIgnoreShaded()) continue;
621 } else if (curr->isMaximizedFull()) {
622 if (screen->getPlaceIgnoreMaximized()) continue;
625 tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
626 curr->frameRect().width() + screen->getBorderWidth(),
627 curr->frameRect().height() + screen->getBorderWidth());
629 spaces = calcSpace(tmp, spaces);
632 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
633 if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
634 if(screen->getColPlacementDirection() == BScreen::TopBottom)
635 std::sort(spaces.begin(), spaces.end(), rowLRTB);
637 std::sort(spaces.begin(), spaces.end(), rowLRBT);
639 if(screen->getColPlacementDirection() == BScreen::TopBottom)
640 std::sort(spaces.begin(), spaces.end(), rowRLTB);
642 std::sort(spaces.begin(), spaces.end(), rowRLBT);
645 if(screen->getColPlacementDirection() == BScreen::TopBottom) {
646 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
647 std::sort(spaces.begin(), spaces.end(), colLRTB);
649 std::sort(spaces.begin(), spaces.end(), colRLTB);
651 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
652 std::sort(spaces.begin(), spaces.end(), colLRBT);
654 std::sort(spaces.begin(), spaces.end(), colRLBT);
658 rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
659 for(; sit != spaces_end; ++sit) {
660 if (sit->width() >= win.width() && sit->height() >= win.height())
664 if (sit == spaces_end)
667 //set new position based on the empty space found
668 const Rect& where = *sit;
672 // adjust the location() based on left/right and top/bottom placement
673 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
674 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
675 win.setX(where.right() - win.width());
676 if (screen->getColPlacementDirection() == BScreen::BottomTop)
677 win.setY(where.bottom() - win.height());
679 if (screen->getColPlacementDirection() == BScreen::BottomTop)
680 win.setY(win.y() + where.height() - win.height());
681 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
682 win.setX(win.x() + where.width() - win.width());
688 bool Workspace::underMousePlacement(Rect &win) {
692 XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
693 &r, &c, &rx, &ry, &x, &y, &m);
697 if (screen->isXineramaActive() &&
698 screen->getBlackbox()->doXineramaPlacement()) {
699 RectList availableAreas = screen->allAvailableAreas();
700 RectList::iterator it, end = availableAreas.end();
702 for (it = availableAreas.begin(); it != end; ++it)
703 if (it->contains(rx, ry)) break;
704 assert(it != end); // the mouse isn't inside an area?
708 area = screen->availableArea();
710 x = rx - win.width() / 2;
711 y = ry - win.height() / 2;
717 if (x + win.width() > area.x() + area.width())
718 x = area.x() + area.width() - win.width();
719 if (y + win.height() > area.y() + area.height())
720 y = area.y() + area.height() - win.height();
729 bool Workspace::cascadePlacement(Rect &win, const int offset) {
733 if (screen->isXineramaActive() &&
734 screen->getBlackbox()->doXineramaPlacement()) {
735 area = screen->allAvailableAreas()[cascade_region];
738 area = screen->availableArea();
740 if ((static_cast<signed>(cascade_x + win.width()) > area.right() + 1) ||
741 (static_cast<signed>(cascade_y + win.height()) > area.bottom() + 1)) {
742 cascade_x = cascade_y = 0;
744 if (screen->isXineramaActive() &&
745 screen->getBlackbox()->doXineramaPlacement()) {
746 // go to the next xinerama region, and use its area
747 if (++cascade_region >= screen->allAvailableAreas().size())
749 area = screen->allAvailableAreas()[cascade_region];
754 if (cascade_x == 0) {
755 cascade_x = area.x() + offset;
756 cascade_y = area.y() + offset;
759 win.setPos(cascade_x, cascade_y);
768 void Workspace::placeWindow(BlackboxWindow *win) {
769 Rect new_win(0, 0, win->frameRect().width(), win->frameRect().height());
772 switch (screen->getPlacementPolicy()) {
773 case BScreen::RowSmartPlacement:
774 case BScreen::ColSmartPlacement:
775 placed = smartPlacement(new_win);
777 case BScreen::UnderMousePlacement:
778 case BScreen::ClickMousePlacement:
779 placed = underMousePlacement(new_win);
781 break; // handled below
785 cascadePlacement(new_win, (win->getTitleHeight() +
786 screen->getBorderWidth() * 2));
788 if (new_win.right() > screen->availableArea().right())
789 new_win.setX(screen->availableArea().left());
790 if (new_win.bottom() > screen->availableArea().bottom())
791 new_win.setY(screen->availableArea().top());
793 win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());