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"
33 #include "workspace.hh"
38 Workspace::Workspace(BScreen
*scrn
, unsigned int i
) {
40 xatom
= screen
->getBlackbox()->getXAtom();
42 cascade_x
= cascade_y
= 0;
49 lastfocus
= (BlackboxWindow
*) 0;
55 void Workspace::addWindow(BlackboxWindow
*w
, bool place
, bool sticky
) {
58 if (place
) placeWindow(w
);
60 stackingList
.push_front(w
);
65 if (! w
->isNormal()) {
67 // just give it some number, else bad things happen as it is assumed to
68 // not be on a workspace
69 w
->setWindowNumber(0);
73 w
->setWindowNumber(windowList
.size());
75 windowList
.push_back(w
);
77 if (screen
->doFocusNew() || (w
->isTransient() && w
->getTransientFor() &&
78 w
->getTransientFor()->isFocused())) {
79 if (id
!= screen
->getCurrentWorkspaceID()) {
81 not on the focused workspace, so the window is not going to get focus
82 but if the user wants new windows focused, then it should get focus
83 when this workspace does become focused.
97 void Workspace::removeWindow(BlackboxWindow
*w
, bool sticky
) {
100 stackingList
.remove(w
);
102 // pass focus to the next appropriate window
103 if ((w
->isFocused() || w
== lastfocus
) &&
104 screen
->getBlackbox()->state() != Openbox::State_Exiting
) {
108 if (! w
->isNormal()) return;
110 BlackboxWindowList::iterator it
, end
= windowList
.end();
112 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
117 windowList
.erase(it
);
119 BlackboxWindowList::iterator it
= windowList
.begin();
120 const BlackboxWindowList::iterator end
= windowList
.end();
122 for (; it
!= end
; ++it
, ++i
)
123 (*it
)->setWindowNumber(i
);
127 cascade_x
= cascade_y
= 0;
135 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
136 BlackboxWindow
*newfocus
= 0;
138 if (id
== screen
->getCurrentWorkspaceID()) {
139 // The window is on the visible workspace.
141 // if it's a transient, then try to focus its parent
142 if (old_window
&& old_window
->isTransient()) {
143 newfocus
= old_window
->getTransientFor();
146 newfocus
->isIconic() || // do not focus icons
147 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
148 ! newfocus
->setInputFocus())
153 BlackboxWindowList::iterator it
= stackingList
.begin(),
154 end
= stackingList
.end();
155 for (; it
!= end
; ++it
) {
156 BlackboxWindow
*tmp
= *it
;
157 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
158 // we found our new focus target
165 screen
->getBlackbox()->setFocusedWindow(newfocus
);
167 // The window is not on the visible workspace.
169 if (old_window
&& lastfocus
== old_window
) {
170 // The window was the last-focus target, so we need to replace it.
171 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
172 if (! stackingList
.empty())
173 win
= stackingList
.front();
174 setLastFocusedWindow(win
);
180 void Workspace::removeAll(void) {
181 while (! windowList
.empty())
182 windowList
.front()->iconify();
185 void Workspace::showAll(void) {
186 BlackboxWindowList::iterator it
= stackingList
.begin();
187 const BlackboxWindowList::iterator end
= stackingList
.end();
188 for (; it
!= end
; ++it
) {
189 BlackboxWindow
*bw
= *it
;
190 // sticky windows arent unmapped on a workspace change so we don't have ot
191 // map them, but sometimes on a restart, another app can unmap our sticky
192 // windows, so we map on startup always
193 if (! bw
->isStuck() ||
194 screen
->getBlackbox()->state() == Openbox::State_Starting
)
200 void Workspace::hideAll(void) {
201 // withdraw in reverse order to minimize the number of Expose events
203 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
205 BlackboxWindowList::iterator it
= lst
.begin();
206 const BlackboxWindowList::iterator end
= lst
.end();
207 for (; it
!= end
; ++it
) {
208 BlackboxWindow
*bw
= *it
;
209 // don't hide sticky windows, or they'll end up flickering on a workspace
219 * returns the number of transients for win, plus the number of transients
220 * associated with each transient of win
222 static unsigned int countTransients(const BlackboxWindow
* const win
) {
223 BlackboxWindowList transients
= win
->getTransients();
224 if (transients
.empty()) return 0;
226 unsigned int ret
= transients
.size();
227 BlackboxWindowList::const_iterator it
= transients
.begin(),
228 end
= transients
.end();
229 for (; it
!= end
; ++it
)
230 ret
+= countTransients(*it
);
237 * puts the transients of win into the stack. windows are stacked above
238 * the window before it in the stackvector being iterated, meaning
239 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
242 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
243 StackVector::iterator
&stack
) {
244 if (win
->getTransients().empty()) return; // nothing to do
246 // put win's transients in the stack
247 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
248 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
249 BlackboxWindow
*w
= *it
;
250 *stack
++ = w
->getFrameWindow();
252 if (! w
->isIconic()) {
253 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
254 wkspc
->stackingList
.remove(w
);
255 wkspc
->stackingList
.push_front(w
);
259 // put transients of win's transients in the stack
260 for (it
= win
->getTransients().begin(); it
!= end
; ++it
)
261 raiseTransients(*it
, stack
);
265 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
266 StackVector::iterator
&stack
) {
267 if (win
->getTransients().empty()) return; // nothing to do
269 // put transients of win's transients in the stack
270 BlackboxWindowList::const_reverse_iterator it
,
271 end
= win
->getTransients().rend();
272 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
)
273 lowerTransients(*it
, stack
);
275 // put win's transients in the stack
276 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
277 BlackboxWindow
*w
= *it
;
278 *stack
++ = w
->getFrameWindow();
280 if (! w
->isIconic()) {
281 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
282 wkspc
->stackingList
.remove(w
);
283 wkspc
->stackingList
.push_back(w
);
289 void Workspace::raiseWindow(BlackboxWindow
*w
) {
290 BlackboxWindow
*win
= w
;
292 if (win
->isDesktop()) return;
294 // walk up the transient_for's to the window that is not a transient
295 while (win
->isTransient() && win
->getTransientFor())
296 win
= win
->getTransientFor();
298 // get the total window count (win and all transients)
299 unsigned int i
= 1 + countTransients(win
);
301 // stack the window with all transients above
302 StackVector
stack_vector(i
);
303 StackVector::iterator stack
= stack_vector
.begin();
305 *(stack
++) = win
->getFrameWindow();
306 if (! (win
->isIconic() || win
->isDesktop())) {
307 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
308 wkspc
->stackingList
.remove(win
);
309 wkspc
->stackingList
.push_front(win
);
312 raiseTransients(win
, stack
);
314 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
318 void Workspace::lowerWindow(BlackboxWindow
*w
) {
319 BlackboxWindow
*win
= w
;
321 // walk up the transient_for's to the window that is not a transient
322 while (win
->isTransient() && win
->getTransientFor())
323 win
= win
->getTransientFor();
325 // get the total window count (win and all transients)
326 unsigned int i
= 1 + countTransients(win
);
328 // stack the window with all transients above
329 StackVector
stack_vector(i
);
330 StackVector::iterator stack
= stack_vector
.begin();
332 lowerTransients(win
, stack
);
334 *(stack
++) = win
->getFrameWindow();
335 if (! (win
->isIconic() || win
->isDesktop())) {
336 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
337 wkspc
->stackingList
.remove(win
);
338 wkspc
->stackingList
.push_back(win
);
341 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
345 void Workspace::reconfigure(void) {
346 std::for_each(windowList
.begin(), windowList
.end(),
347 std::mem_fun(&BlackboxWindow::reconfigure
));
351 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
352 if (index
< windowList
.size()) {
353 BlackboxWindowList::iterator it
= windowList
.begin();
354 while (index
-- > 0) // increment to index
364 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
365 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
368 assert(it
!= windowList
.end()); // window must be in list
370 if (it
== windowList
.end())
371 return windowList
.front(); // if we walked off the end, wrap around
377 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
378 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
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
389 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
390 assert(! stackingList
.empty());
391 return stackingList
.front();
395 unsigned int Workspace::getCount(void) const {
396 return windowList
.size();
400 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
401 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
402 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
403 for (; it
!= end
; ++it
)
404 // don't add desktop wnidows, or sticky windows more than once
405 if (! ( (*it
)->isDesktop() ||
406 ((*it
)->isStuck() && id
!= screen
->getCurrentWorkspaceID())))
407 stack_order
.push_back(*it
);
411 bool Workspace::isCurrent(void) const {
412 return (id
== screen
->getCurrentWorkspaceID());
416 bool Workspace::isLastWindow(const BlackboxWindow
*w
) const {
417 return (w
== windowList
.back());
421 void Workspace::setCurrent(void) {
422 screen
->changeWorkspaceID(id
);
426 void Workspace::readName(void) {
427 XAtom::StringVect namesList
;
428 unsigned long numnames
= id
+ 1;
430 // attempt to get from the _NET_WM_DESKTOP_NAMES property
431 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
432 XAtom::utf8
, numnames
, namesList
) &&
433 namesList
.size() > id
) {
434 name
= namesList
[id
];
438 Use a default name. This doesn't actually change the class. That will
439 happen after the setName changes the root property, and that change
440 makes its way back to this function.
442 string tmp
= "Workspace %d";
443 assert(tmp
.length() < 32);
444 char default_name
[32];
445 sprintf(default_name
, tmp
.c_str(), id
+ 1);
447 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
452 void Workspace::setName(const string
& new_name
) {
453 // set the _NET_WM_DESKTOP_NAMES property with the new name
454 XAtom::StringVect namesList
;
455 unsigned long numnames
= (unsigned) -1;
456 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
457 XAtom::utf8
, numnames
, namesList
) &&
458 namesList
.size() > id
)
459 namesList
[id
] = new_name
;
461 namesList
.push_back(new_name
);
463 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
464 XAtom::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
->getBorderWidth(),
609 curr
->frameRect().height() + screen
->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
->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());