1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
4 # include "../config.h"
5 #endif // HAVE_CONFIG_H
13 #endif // HAVE_STDIO_H
17 #endif // HAVE_STRING_H
27 #include "blackbox.hh"
32 #include "workspace.hh"
36 Workspace::Workspace(BScreen
*scrn
, unsigned int i
) {
38 xatom
= screen
->getBlackbox()->getXAtom();
40 cascade_x
= cascade_y
= 0;
47 lastfocus
= (BlackboxWindow
*) 0;
53 void Workspace::addWindow(BlackboxWindow
*w
, bool place
, bool sticky
) {
56 if (place
) placeWindow(w
);
58 stackingList
.push_front(w
);
63 if (! w
->isNormal()) {
65 // just give it some number, else bad things happen as it is assumed to
66 // not be on a workspace
67 w
->setWindowNumber(0);
71 w
->setWindowNumber(windowList
.size());
73 windowList
.push_back(w
);
75 if (screen
->doFocusNew() || (w
->isTransient() && w
->getTransientFor() &&
76 w
->getTransientFor()->isFocused())) {
77 if (id
!= screen
->getCurrentWorkspaceID()) {
79 not on the focused workspace, so the window is not going to get focus
80 but if the user wants new windows focused, then it should get focus
81 when this workspace does become focused.
95 void Workspace::removeWindow(BlackboxWindow
*w
, bool sticky
) {
98 stackingList
.remove(w
);
100 // pass focus to the next appropriate window
101 if ((w
->isFocused() || w
== lastfocus
) &&
102 ! screen
->getBlackbox()->doShutdown()) {
106 if (! w
->isNormal()) return;
108 BlackboxWindowList::iterator it
, end
= windowList
.end();
110 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
115 windowList
.erase(it
);
117 BlackboxWindowList::iterator it
= windowList
.begin();
118 const BlackboxWindowList::iterator end
= windowList
.end();
120 for (; it
!= end
; ++it
, ++i
)
121 (*it
)->setWindowNumber(i
);
125 cascade_x
= cascade_y
= 0;
133 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
134 BlackboxWindow
*newfocus
= 0;
136 if (id
== screen
->getCurrentWorkspaceID()) {
137 // The window is on the visible workspace.
139 // if it's a transient, then try to focus its parent
140 if (old_window
&& old_window
->isTransient()) {
141 newfocus
= old_window
->getTransientFor();
144 newfocus
->isIconic() || // do not focus icons
145 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
146 ! newfocus
->setInputFocus())
151 BlackboxWindowList::iterator it
= stackingList
.begin(),
152 end
= stackingList
.end();
153 for (; it
!= end
; ++it
) {
154 BlackboxWindow
*tmp
= *it
;
155 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
156 // we found our new focus target
163 screen
->getBlackbox()->setFocusedWindow(newfocus
);
165 // The window is not on the visible workspace.
167 if (old_window
&& lastfocus
== old_window
) {
168 // The window was the last-focus target, so we need to replace it.
169 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
170 if (! stackingList
.empty())
171 win
= stackingList
.front();
172 setLastFocusedWindow(win
);
178 void Workspace::removeAll(void) {
179 while (! windowList
.empty())
180 windowList
.front()->iconify();
183 void Workspace::showAll(void) {
184 BlackboxWindowList::iterator it
= stackingList
.begin();
185 const BlackboxWindowList::iterator end
= stackingList
.end();
186 for (; it
!= end
; ++it
) {
187 BlackboxWindow
*bw
= *it
;
188 // sticky windows arent unmapped on a workspace change so we don't have ot
189 // map them, but sometimes on a restart, another app can unmap our sticky
190 // windows, so we map on startup always
191 if (! bw
->isStuck() || screen
->getBlackbox()->isStartup())
197 void Workspace::hideAll(void) {
198 // withdraw in reverse order to minimize the number of Expose events
200 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
202 BlackboxWindowList::iterator it
= lst
.begin();
203 const BlackboxWindowList::iterator end
= lst
.end();
204 for (; it
!= end
; ++it
) {
205 BlackboxWindow
*bw
= *it
;
206 // don't hide sticky windows, or they'll end up flickering on a workspace
216 * returns the number of transients for win, plus the number of transients
217 * associated with each transient of win
219 static unsigned int countTransients(const BlackboxWindow
* const win
) {
220 BlackboxWindowList transients
= win
->getTransients();
221 if (transients
.empty()) return 0;
223 unsigned int ret
= transients
.size();
224 BlackboxWindowList::const_iterator it
= transients
.begin(),
225 end
= transients
.end();
226 for (; it
!= end
; ++it
)
227 ret
+= countTransients(*it
);
234 * puts the transients of win into the stack. windows are stacked above
235 * the window before it in the stackvector being iterated, meaning
236 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
239 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
240 StackVector::iterator
&stack
) {
241 if (win
->getTransients().empty()) return; // nothing to do
243 // put win's transients in the stack
244 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
245 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
246 BlackboxWindow
*w
= *it
;
247 *stack
++ = w
->getFrameWindow();
249 if (! w
->isIconic()) {
250 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
251 wkspc
->stackingList
.remove(w
);
252 wkspc
->stackingList
.push_front(w
);
256 // put transients of win's transients in the stack
257 for (it
= win
->getTransients().begin(); it
!= end
; ++it
)
258 raiseTransients(*it
, stack
);
262 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
263 StackVector::iterator
&stack
) {
264 if (win
->getTransients().empty()) return; // nothing to do
266 // put transients of win's transients in the stack
267 BlackboxWindowList::const_reverse_iterator it
,
268 end
= win
->getTransients().rend();
269 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
)
270 lowerTransients(*it
, stack
);
272 // put win's transients in the stack
273 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
274 BlackboxWindow
*w
= *it
;
275 *stack
++ = w
->getFrameWindow();
277 if (! w
->isIconic()) {
278 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
279 wkspc
->stackingList
.remove(w
);
280 wkspc
->stackingList
.push_back(w
);
286 void Workspace::raiseWindow(BlackboxWindow
*w
) {
287 BlackboxWindow
*win
= w
;
289 if (win
->isDesktop()) return;
291 // walk up the transient_for's to the window that is not a transient
292 while (win
->isTransient() && win
->getTransientFor())
293 win
= win
->getTransientFor();
295 // get the total window count (win and all transients)
296 unsigned int i
= 1 + countTransients(win
);
298 // stack the window with all transients above
299 StackVector
stack_vector(i
);
300 StackVector::iterator stack
= stack_vector
.begin();
302 *(stack
++) = win
->getFrameWindow();
303 if (! (win
->isIconic() || win
->isDesktop())) {
304 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
305 wkspc
->stackingList
.remove(win
);
306 wkspc
->stackingList
.push_front(win
);
309 raiseTransients(win
, stack
);
311 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
315 void Workspace::lowerWindow(BlackboxWindow
*w
) {
316 BlackboxWindow
*win
= w
;
318 // walk up the transient_for's to the window that is not a transient
319 while (win
->isTransient() && win
->getTransientFor())
320 win
= win
->getTransientFor();
322 // get the total window count (win and all transients)
323 unsigned int i
= 1 + countTransients(win
);
325 // stack the window with all transients above
326 StackVector
stack_vector(i
);
327 StackVector::iterator stack
= stack_vector
.begin();
329 lowerTransients(win
, stack
);
331 *(stack
++) = win
->getFrameWindow();
332 if (! (win
->isIconic() || win
->isDesktop())) {
333 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
334 wkspc
->stackingList
.remove(win
);
335 wkspc
->stackingList
.push_back(win
);
338 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
342 void Workspace::reconfigure(void) {
343 std::for_each(windowList
.begin(), windowList
.end(),
344 std::mem_fun(&BlackboxWindow::reconfigure
));
348 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
349 if (index
< windowList
.size()) {
350 BlackboxWindowList::iterator it
= windowList
.begin();
351 while (index
-- > 0) // increment to index
361 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
362 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
365 assert(it
!= windowList
.end()); // window must be in list
367 if (it
== windowList
.end())
368 return windowList
.front(); // if we walked off the end, wrap around
374 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
375 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
378 assert(it
!= windowList
.end()); // window must be in list
379 if (it
== windowList
.begin())
380 return windowList
.back(); // if we walked of the front, wrap around
386 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
387 assert(! stackingList
.empty());
388 return stackingList
.front();
392 unsigned int Workspace::getCount(void) const {
393 return windowList
.size();
397 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
398 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
399 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
400 for (; it
!= end
; ++it
)
401 // don't add desktop wnidows, or sticky windows more than once
402 if (! ( (*it
)->isDesktop() ||
403 ((*it
)->isStuck() && id
!= screen
->getCurrentWorkspaceID())))
404 stack_order
.push_back(*it
);
408 bool Workspace::isCurrent(void) const {
409 return (id
== screen
->getCurrentWorkspaceID());
413 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
414 return (w
== windowList
.back());
418 void Workspace::setCurrent(void) {
419 screen
->changeWorkspaceID(id
);
423 void Workspace::readName(void) {
424 XAtom::StringVect namesList
;
425 unsigned long numnames
= id
+ 1;
427 // attempt to get from the _NET_WM_DESKTOP_NAMES property
428 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
429 XAtom::utf8
, numnames
, namesList
) &&
430 namesList
.size() > id
) {
431 name
= namesList
[id
];
435 Use a default name. This doesn't actually change the class. That will
436 happen after the setName changes the root property, and that change
437 makes its way back to this function.
439 string tmp
= "Workspace %d";
440 assert(tmp
.length() < 32);
441 char default_name
[32];
442 sprintf(default_name
, tmp
.c_str(), id
+ 1);
444 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
449 void Workspace::setName(const string
& new_name
) {
450 // set the _NET_WM_DESKTOP_NAMES property with the new name
451 XAtom::StringVect namesList
;
452 unsigned long numnames
= (unsigned) -1;
453 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
454 XAtom::utf8
, numnames
, namesList
) &&
455 namesList
.size() > id
)
456 namesList
[id
] = new_name
;
458 namesList
.push_back(new_name
);
460 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
461 XAtom::utf8
, namesList
);
466 * Calculate free space available for window placement.
468 Workspace::rectList
Workspace::calcSpace(const Rect
&win
,
469 const rectList
&spaces
) const {
472 rectList::const_iterator siter
, end
= spaces
.end();
473 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
474 const Rect
&curr
= *siter
;
476 if(! win
.intersects(curr
)) {
477 result
.push_back(curr
);
481 /* Use an intersection of win and curr to determine the space around
482 * curr that we can use.
484 * NOTE: the spaces calculated can overlap.
489 extra
.setCoords(curr
.left(), curr
.top(),
490 isect
.left() - screen
->getSnapOffset(), curr
.bottom());
491 if (extra
.valid()) result
.push_back(extra
);
494 extra
.setCoords(curr
.left(), curr
.top(),
495 curr
.right(), isect
.top() - screen
->getSnapOffset());
496 if (extra
.valid()) result
.push_back(extra
);
499 extra
.setCoords(isect
.right() + screen
->getSnapOffset(), curr
.top(),
500 curr
.right(), curr
.bottom());
501 if (extra
.valid()) result
.push_back(extra
);
504 extra
.setCoords(curr
.left(), isect
.bottom() + screen
->getSnapOffset(),
505 curr
.right(), curr
.bottom());
506 if (extra
.valid()) result
.push_back(extra
);
512 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
513 if (first
.bottom() == second
.bottom())
514 return first
.right() > second
.right();
515 return first
.bottom() > second
.bottom();
518 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
519 if (first
.y() == second
.y())
520 return first
.right() > second
.right();
521 return first
.y() < second
.y();
524 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
525 if (first
.bottom() == second
.bottom())
526 return first
.x() < second
.x();
527 return first
.bottom() > second
.bottom();
530 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
531 if (first
.y() == second
.y())
532 return first
.x() < second
.x();
533 return first
.y() < second
.y();
536 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
537 if (first
.x() == second
.x())
538 return first
.y() < second
.y();
539 return first
.x() < second
.x();
542 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
543 if (first
.x() == second
.x())
544 return first
.bottom() > second
.bottom();
545 return first
.x() < second
.x();
548 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
549 if (first
.right() == second
.right())
550 return first
.y() < second
.y();
551 return first
.right() > second
.right();
554 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
555 if (first
.right() == second
.right())
556 return first
.bottom() > second
.bottom();
557 return first
.right() > second
.right();
561 bool Workspace::smartPlacement(Rect
& win
) {
564 //initially the entire screen is free
566 if (screen
->isXineramaActive() &&
567 screen
->getBlackbox()->doXineramaPlacement()) {
568 RectList availableAreas
= screen
->allAvailableAreas();
569 RectList::iterator it
, end
= availableAreas
.end();
571 for (it
= availableAreas
.begin(); it
!= end
; ++it
) {
573 r
.setRect(r
.x() + screen
->getSnapOffset(),
574 r
.y() + screen
->getSnapOffset(),
575 r
.width() - screen
->getSnapOffset(),
576 r
.height() - screen
->getSnapOffset());
577 spaces
.push_back(*it
);
582 Rect r
= screen
->availableArea();
583 r
.setRect(r
.x() + screen
->getSnapOffset(),
584 r
.y() + screen
->getSnapOffset(),
585 r
.width() - screen
->getSnapOffset(),
586 r
.height() - screen
->getSnapOffset());
591 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
592 end
= windowList
.end();
594 for (; wit
!= end
; ++wit
) {
595 const BlackboxWindow
* const curr
= *wit
;
597 // watch for shaded windows and full-maxed windows
598 if (curr
->isShaded()) {
599 if (screen
->getPlaceIgnoreShaded()) continue;
600 } else if (curr
->isMaximizedFull()) {
601 if (screen
->getPlaceIgnoreMaximized()) continue;
604 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
605 curr
->frameRect().width() + screen
->getBorderWidth(),
606 curr
->frameRect().height() + screen
->getBorderWidth());
608 spaces
= calcSpace(tmp
, spaces
);
611 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
612 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
613 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
614 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
616 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
618 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
619 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
621 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
624 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
625 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
626 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
628 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
630 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
631 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
633 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
637 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
638 for(; sit
!= spaces_end
; ++sit
) {
639 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
643 if (sit
== spaces_end
)
646 //set new position based on the empty space found
647 const Rect
& where
= *sit
;
651 // adjust the location() based on left/right and top/bottom placement
652 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
653 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
654 win
.setX(where
.right() - win
.width());
655 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
656 win
.setY(where
.bottom() - win
.height());
658 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
659 win
.setY(win
.y() + where
.height() - win
.height());
660 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
661 win
.setX(win
.x() + where
.width() - win
.width());
667 bool Workspace::underMousePlacement(Rect
&win
) {
671 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
672 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
676 if (screen
->isXineramaActive() &&
677 screen
->getBlackbox()->doXineramaPlacement()) {
678 RectList availableAreas
= screen
->allAvailableAreas();
679 RectList::iterator it
, end
= availableAreas
.end();
681 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
682 if (it
->contains(rx
, ry
)) break;
683 assert(it
!= end
); // the mouse isn't inside an area?
687 area
= screen
->availableArea();
689 x
= rx
- win
.width() / 2;
690 y
= ry
- win
.height() / 2;
696 if (x
+ win
.width() > area
.x() + area
.width())
697 x
= area
.x() + area
.width() - win
.width();
698 if (y
+ win
.height() > area
.y() + area
.height())
699 y
= area
.y() + area
.height() - win
.height();
708 bool Workspace::cascadePlacement(Rect
&win
, const int offset
) {
712 if (screen
->isXineramaActive() &&
713 screen
->getBlackbox()->doXineramaPlacement()) {
714 area
= screen
->allAvailableAreas()[cascade_region
];
717 area
= screen
->availableArea();
719 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
720 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
721 cascade_x
= cascade_y
= 0;
723 if (screen
->isXineramaActive() &&
724 screen
->getBlackbox()->doXineramaPlacement()) {
725 // go to the next xinerama region, and use its area
726 if (++cascade_region
>= screen
->allAvailableAreas().size())
728 area
= screen
->allAvailableAreas()[cascade_region
];
733 if (cascade_x
== 0) {
734 cascade_x
= area
.x() + offset
;
735 cascade_y
= area
.y() + offset
;
738 win
.setPos(cascade_x
, cascade_y
);
747 void Workspace::placeWindow(BlackboxWindow
*win
) {
748 Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
751 switch (screen
->getPlacementPolicy()) {
752 case BScreen::RowSmartPlacement
:
753 case BScreen::ColSmartPlacement
:
754 placed
= smartPlacement(new_win
);
756 case BScreen::UnderMousePlacement
:
757 case BScreen::ClickMousePlacement
:
758 placed
= underMousePlacement(new_win
);
760 break; // handled below
764 cascadePlacement(new_win
, (win
->getTitleHeight() +
765 screen
->getBorderWidth() * 2));
767 if (new_win
.right() > screen
->availableArea().right())
768 new_win
.setX(screen
->availableArea().left());
769 if (new_win
.bottom() > screen
->availableArea().bottom())
770 new_win
.setY(screen
->availableArea().top());
772 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());