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"
28 #include "otk/font.hh"
29 #include "otk/display.hh"
31 #include "otk/util.hh"
32 #include "bbwindow.hh"
33 #include "workspace.hh"
37 Workspace::Workspace(BScreen
*scrn
, unsigned int i
) {
39 xatom
= screen
->getBlackbox()->getXAtom();
41 cascade_x
= cascade_y
= 0;
48 lastfocus
= (BlackboxWindow
*) 0;
54 void Workspace::addWindow(BlackboxWindow
*w
, bool place
, bool sticky
) {
57 if (place
) placeWindow(w
);
59 stackingList
.push_front(w
);
64 if (! w
->isNormal()) {
66 // just give it some number, else bad things happen as it is assumed to
67 // not be on a workspace
68 w
->setWindowNumber(0);
72 w
->setWindowNumber(windowList
.size());
74 windowList
.push_back(w
);
76 if (screen
->doFocusNew() || (w
->isTransient() && w
->getTransientFor() &&
77 w
->getTransientFor()->isFocused())) {
78 if (id
!= screen
->getCurrentWorkspaceID()) {
80 not on the focused workspace, so the window is not going to get focus
81 but if the user wants new windows focused, then it should get focus
82 when this workspace does become focused.
96 void Workspace::removeWindow(BlackboxWindow
*w
, bool sticky
) {
99 stackingList
.remove(w
);
101 // pass focus to the next appropriate window
102 if ((w
->isFocused() || w
== lastfocus
) &&
103 screen
->getBlackbox()->state() != Openbox::State_Exiting
) {
107 if (! w
->isNormal()) return;
109 BlackboxWindowList::iterator it
, end
= windowList
.end();
111 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
116 windowList
.erase(it
);
118 BlackboxWindowList::iterator it
= windowList
.begin();
119 const BlackboxWindowList::iterator end
= windowList
.end();
121 for (; it
!= end
; ++it
, ++i
)
122 (*it
)->setWindowNumber(i
);
126 cascade_x
= cascade_y
= 0;
134 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
135 BlackboxWindow
*newfocus
= 0;
137 if (id
== screen
->getCurrentWorkspaceID()) {
138 // The window is on the visible workspace.
140 // if it's a transient, then try to focus its parent
141 if (old_window
&& old_window
->isTransient()) {
142 newfocus
= old_window
->getTransientFor();
145 newfocus
->isIconic() || // do not focus icons
146 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
147 ! newfocus
->setInputFocus())
152 BlackboxWindowList::iterator it
= stackingList
.begin(),
153 end
= stackingList
.end();
154 for (; it
!= end
; ++it
) {
155 BlackboxWindow
*tmp
= *it
;
156 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
157 // we found our new focus target
164 screen
->getBlackbox()->setFocusedWindow(newfocus
);
166 // The window is not on the visible workspace.
168 if (old_window
&& lastfocus
== old_window
) {
169 // The window was the last-focus target, so we need to replace it.
170 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
171 if (! stackingList
.empty())
172 win
= stackingList
.front();
173 setLastFocusedWindow(win
);
179 void Workspace::removeAll(void) {
180 while (! windowList
.empty())
181 windowList
.front()->iconify();
184 void Workspace::showAll(void) {
185 BlackboxWindowList::iterator it
= stackingList
.begin();
186 const BlackboxWindowList::iterator end
= stackingList
.end();
187 for (; it
!= end
; ++it
) {
188 BlackboxWindow
*bw
= *it
;
189 // sticky windows arent unmapped on a workspace change so we don't have ot
190 // map them, but sometimes on a restart, another app can unmap our sticky
191 // windows, so we map on startup always
192 if (! bw
->isStuck() ||
193 screen
->getBlackbox()->state() == Openbox::State_Starting
)
199 void Workspace::hideAll(void) {
200 // withdraw in reverse order to minimize the number of Expose events
202 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
204 BlackboxWindowList::iterator it
= lst
.begin();
205 const BlackboxWindowList::iterator end
= lst
.end();
206 for (; it
!= end
; ++it
) {
207 BlackboxWindow
*bw
= *it
;
208 // don't hide sticky windows, or they'll end up flickering on a workspace
218 * returns the number of transients for win, plus the number of transients
219 * associated with each transient of win
221 static unsigned int countTransients(const BlackboxWindow
* const win
) {
222 BlackboxWindowList transients
= win
->getTransients();
223 if (transients
.empty()) return 0;
225 unsigned int ret
= transients
.size();
226 BlackboxWindowList::const_iterator it
= transients
.begin(),
227 end
= transients
.end();
228 for (; it
!= end
; ++it
)
229 ret
+= countTransients(*it
);
236 * puts the transients of win into the stack. windows are stacked above
237 * the window before it in the stackvector being iterated, meaning
238 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
241 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
242 StackVector::iterator
&stack
) {
243 if (win
->getTransients().empty()) return; // nothing to do
245 // put win's transients in the stack
246 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
247 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
248 BlackboxWindow
*w
= *it
;
249 *stack
++ = w
->getFrameWindow();
251 if (! w
->isIconic()) {
252 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
253 wkspc
->stackingList
.remove(w
);
254 wkspc
->stackingList
.push_front(w
);
258 // put transients of win's transients in the stack
259 for (it
= win
->getTransients().begin(); it
!= end
; ++it
)
260 raiseTransients(*it
, stack
);
264 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
265 StackVector::iterator
&stack
) {
266 if (win
->getTransients().empty()) return; // nothing to do
268 // put transients of win's transients in the stack
269 BlackboxWindowList::const_reverse_iterator it
,
270 end
= win
->getTransients().rend();
271 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
)
272 lowerTransients(*it
, stack
);
274 // put win's transients in the stack
275 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
276 BlackboxWindow
*w
= *it
;
277 *stack
++ = w
->getFrameWindow();
279 if (! w
->isIconic()) {
280 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
281 wkspc
->stackingList
.remove(w
);
282 wkspc
->stackingList
.push_back(w
);
288 void Workspace::raiseWindow(BlackboxWindow
*w
) {
289 BlackboxWindow
*win
= w
;
291 if (win
->isDesktop()) return;
293 // walk up the transient_for's to the window that is not a transient
294 while (win
->isTransient() && win
->getTransientFor())
295 win
= win
->getTransientFor();
297 // get the total window count (win and all transients)
298 unsigned int i
= 1 + countTransients(win
);
300 // stack the window with all transients above
301 StackVector
stack_vector(i
);
302 StackVector::iterator stack
= stack_vector
.begin();
304 *(stack
++) = win
->getFrameWindow();
305 if (! (win
->isIconic() || win
->isDesktop())) {
306 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
307 wkspc
->stackingList
.remove(win
);
308 wkspc
->stackingList
.push_front(win
);
311 raiseTransients(win
, stack
);
313 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
317 void Workspace::lowerWindow(BlackboxWindow
*w
) {
318 BlackboxWindow
*win
= w
;
320 // walk up the transient_for's to the window that is not a transient
321 while (win
->isTransient() && win
->getTransientFor())
322 win
= win
->getTransientFor();
324 // get the total window count (win and all transients)
325 unsigned int i
= 1 + countTransients(win
);
327 // stack the window with all transients above
328 StackVector
stack_vector(i
);
329 StackVector::iterator stack
= stack_vector
.begin();
331 lowerTransients(win
, stack
);
333 *(stack
++) = win
->getFrameWindow();
334 if (! (win
->isIconic() || win
->isDesktop())) {
335 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
336 wkspc
->stackingList
.remove(win
);
337 wkspc
->stackingList
.push_back(win
);
340 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
344 void Workspace::reconfigure(void) {
345 std::for_each(windowList
.begin(), windowList
.end(),
346 std::mem_fun(&BlackboxWindow::reconfigure
));
350 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
351 if (index
< windowList
.size()) {
352 BlackboxWindowList::iterator it
= windowList
.begin();
353 while (index
-- > 0) // increment to index
363 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
364 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
367 assert(it
!= windowList
.end()); // window must be in list
369 if (it
== windowList
.end())
370 return windowList
.front(); // if we walked off the end, wrap around
376 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
377 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
380 assert(it
!= windowList
.end()); // window must be in list
381 if (it
== windowList
.begin())
382 return windowList
.back(); // if we walked of the front, wrap around
388 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
389 assert(! stackingList
.empty());
390 return stackingList
.front();
394 unsigned int Workspace::getCount(void) const {
395 return windowList
.size();
399 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
400 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
401 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
402 for (; it
!= end
; ++it
)
403 // don't add desktop wnidows, or sticky windows more than once
404 if (! ( (*it
)->isDesktop() ||
405 ((*it
)->isStuck() && id
!= screen
->getCurrentWorkspaceID())))
406 stack_order
.push_back(*it
);
410 bool Workspace::isCurrent(void) const {
411 return (id
== screen
->getCurrentWorkspaceID());
415 bool Workspace::isLastWindow(const BlackboxWindow
*w
) const {
416 return (w
== windowList
.back());
420 void Workspace::setCurrent(void) {
421 screen
->changeWorkspaceID(id
);
425 void Workspace::readName(void) {
426 otk::OBProperty::StringVect namesList
;
427 unsigned long numnames
= id
+ 1;
429 // attempt to get from the _NET_WM_DESKTOP_NAMES property
430 if (xatom
->get(screen
->getRootWindow(), otk::OBProperty::net_desktop_names
,
431 otk::OBProperty::utf8
, &numnames
, &namesList
) &&
432 namesList
.size() > id
) {
433 name
= namesList
[id
];
437 Use a default name. This doesn't actually change the class. That will
438 happen after the setName changes the root property, and that change
439 makes its way back to this function.
441 string tmp
= "Workspace %d";
442 assert(tmp
.length() < 32);
443 char default_name
[32];
444 sprintf(default_name
, tmp
.c_str(), id
+ 1);
446 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
451 void Workspace::setName(const string
& new_name
) {
452 // set the _NET_WM_DESKTOP_NAMES property with the new name
453 otk::OBProperty::StringVect namesList
;
454 unsigned long numnames
= (unsigned) -1;
455 if (xatom
->get(screen
->getRootWindow(),
456 otk::OBProperty::net_desktop_names
,
457 otk::OBProperty::utf8
, &numnames
, &namesList
) &&
458 namesList
.size() > id
)
459 namesList
[id
] = new_name
;
461 namesList
.push_back(new_name
);
463 xatom
->set(screen
->getRootWindow(), otk::OBProperty::net_desktop_names
,
464 otk::OBProperty::utf8
, namesList
);
469 * Calculate free space available for window placement.
471 Workspace::rectList
Workspace::calcSpace(const otk::Rect
&win
,
472 const rectList
&spaces
) const {
473 otk::Rect isect
, extra
;
475 rectList::const_iterator siter
, end
= spaces
.end();
476 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
477 const otk::Rect
&curr
= *siter
;
479 if(! win
.intersects(curr
)) {
480 result
.push_back(curr
);
484 /* Use an intersection of win and curr to determine the space around
485 * curr that we can use.
487 * NOTE: the spaces calculated can overlap.
492 extra
.setCoords(curr
.left(), curr
.top(),
493 isect
.left() - screen
->getSnapOffset(), curr
.bottom());
494 if (extra
.valid()) result
.push_back(extra
);
497 extra
.setCoords(curr
.left(), curr
.top(),
498 curr
.right(), isect
.top() - screen
->getSnapOffset());
499 if (extra
.valid()) result
.push_back(extra
);
502 extra
.setCoords(isect
.right() + screen
->getSnapOffset(), curr
.top(),
503 curr
.right(), curr
.bottom());
504 if (extra
.valid()) result
.push_back(extra
);
507 extra
.setCoords(curr
.left(), isect
.bottom() + screen
->getSnapOffset(),
508 curr
.right(), curr
.bottom());
509 if (extra
.valid()) result
.push_back(extra
);
515 static bool rowRLBT(const otk::Rect
&first
, const otk::Rect
&second
) {
516 if (first
.bottom() == second
.bottom())
517 return first
.right() > second
.right();
518 return first
.bottom() > second
.bottom();
521 static bool rowRLTB(const otk::Rect
&first
, const otk::Rect
&second
) {
522 if (first
.y() == second
.y())
523 return first
.right() > second
.right();
524 return first
.y() < second
.y();
527 static bool rowLRBT(const otk::Rect
&first
, const otk::Rect
&second
) {
528 if (first
.bottom() == second
.bottom())
529 return first
.x() < second
.x();
530 return first
.bottom() > second
.bottom();
533 static bool rowLRTB(const otk::Rect
&first
, const otk::Rect
&second
) {
534 if (first
.y() == second
.y())
535 return first
.x() < second
.x();
536 return first
.y() < second
.y();
539 static bool colLRTB(const otk::Rect
&first
, const otk::Rect
&second
) {
540 if (first
.x() == second
.x())
541 return first
.y() < second
.y();
542 return first
.x() < second
.x();
545 static bool colLRBT(const otk::Rect
&first
, const otk::Rect
&second
) {
546 if (first
.x() == second
.x())
547 return first
.bottom() > second
.bottom();
548 return first
.x() < second
.x();
551 static bool colRLTB(const otk::Rect
&first
, const otk::Rect
&second
) {
552 if (first
.right() == second
.right())
553 return first
.y() < second
.y();
554 return first
.right() > second
.right();
557 static bool colRLBT(const otk::Rect
&first
, const otk::Rect
&second
) {
558 if (first
.right() == second
.right())
559 return first
.bottom() > second
.bottom();
560 return first
.right() > second
.right();
564 bool Workspace::smartPlacement(otk::Rect
& win
) {
567 //initially the entire screen is free
569 if (screen
->isXineramaActive() &&
570 screen
->getBlackbox()->doXineramaPlacement()) {
571 RectList availableAreas
= screen
->allAvailableAreas();
572 RectList::iterator it
, end
= availableAreas
.end();
574 for (it
= availableAreas
.begin(); it
!= end
; ++it
) {
576 r
.setRect(r
.x() + screen
->getSnapOffset(),
577 r
.y() + screen
->getSnapOffset(),
578 r
.width() - screen
->getSnapOffset(),
579 r
.height() - screen
->getSnapOffset());
580 spaces
.push_back(*it
);
585 otk::Rect r
= screen
->availableArea();
586 r
.setRect(r
.x() + screen
->getSnapOffset(),
587 r
.y() + screen
->getSnapOffset(),
588 r
.width() - screen
->getSnapOffset(),
589 r
.height() - screen
->getSnapOffset());
594 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
595 end
= windowList
.end();
597 for (; wit
!= end
; ++wit
) {
598 const BlackboxWindow
* const curr
= *wit
;
600 // watch for shaded windows and full-maxed windows
601 if (curr
->isShaded()) {
602 if (screen
->getPlaceIgnoreShaded()) continue;
603 } else if (curr
->isMaximizedFull()) {
604 if (screen
->getPlaceIgnoreMaximized()) continue;
607 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
608 curr
->frameRect().width() + screen
->getWindowStyle()->getBorderWidth(),
609 curr
->frameRect().height() + screen
->getWindowStyle()->getBorderWidth());
611 spaces
= calcSpace(tmp
, spaces
);
614 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
615 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
616 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
617 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
619 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
621 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
622 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
624 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
627 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
628 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
629 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
631 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
633 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
634 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
636 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
640 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
641 for(; sit
!= spaces_end
; ++sit
) {
642 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
646 if (sit
== spaces_end
)
649 //set new position based on the empty space found
650 const otk::Rect
& where
= *sit
;
654 // adjust the location() based on left/right and top/bottom placement
655 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
656 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
657 win
.setX(where
.right() - win
.width());
658 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
659 win
.setY(where
.bottom() - win
.height());
661 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
662 win
.setY(win
.y() + where
.height() - win
.height());
663 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
664 win
.setX(win
.x() + where
.width() - win
.width());
670 bool Workspace::underMousePlacement(otk::Rect
&win
) {
674 XQueryPointer(otk::OBDisplay::display
, screen
->getRootWindow(),
675 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
679 if (screen
->isXineramaActive() &&
680 screen
->getBlackbox()->doXineramaPlacement()) {
681 RectList availableAreas
= screen
->allAvailableAreas();
682 RectList::iterator it
, end
= availableAreas
.end();
684 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
685 if (it
->contains(rx
, ry
)) break;
686 assert(it
!= end
); // the mouse isn't inside an area?
690 area
= screen
->availableArea();
692 x
= rx
- win
.width() / 2;
693 y
= ry
- win
.height() / 2;
699 if (x
+ win
.width() > area
.x() + area
.width())
700 x
= area
.x() + area
.width() - win
.width();
701 if (y
+ win
.height() > area
.y() + area
.height())
702 y
= area
.y() + area
.height() - win
.height();
711 bool Workspace::cascadePlacement(otk::Rect
&win
, const int offset
) {
715 if (screen
->isXineramaActive() &&
716 screen
->getBlackbox()->doXineramaPlacement()) {
717 area
= screen
->allAvailableAreas()[cascade_region
];
720 area
= screen
->availableArea();
722 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
723 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
724 cascade_x
= cascade_y
= 0;
726 if (screen
->isXineramaActive() &&
727 screen
->getBlackbox()->doXineramaPlacement()) {
728 // go to the next xinerama region, and use its area
729 if (++cascade_region
>= screen
->allAvailableAreas().size())
731 area
= screen
->allAvailableAreas()[cascade_region
];
736 if (cascade_x
== 0) {
737 cascade_x
= area
.x() + offset
;
738 cascade_y
= area
.y() + offset
;
741 win
.setPos(cascade_x
, cascade_y
);
750 void Workspace::placeWindow(BlackboxWindow
*win
) {
751 otk::Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
754 switch (screen
->getPlacementPolicy()) {
755 case BScreen::RowSmartPlacement
:
756 case BScreen::ColSmartPlacement
:
757 placed
= smartPlacement(new_win
);
759 case BScreen::UnderMousePlacement
:
760 case BScreen::ClickMousePlacement
:
761 placed
= underMousePlacement(new_win
);
763 break; // handled below
767 cascadePlacement(new_win
, (win
->getTitleHeight() +
768 screen
->getWindowStyle()->getBorderWidth() * 2));
770 if (new_win
.right() > screen
->availableArea().right())
771 new_win
.setX(screen
->availableArea().left());
772 if (new_win
.bottom() > screen
->availableArea().bottom())
773 new_win
.setY(screen
->availableArea().top());
775 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());