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())
124 void Workspace::removeWindow(BlackboxWindow
*w
, bool sticky
) {
127 stackingList
.remove(w
);
129 // pass focus to the next appropriate window
130 if ((w
->isFocused() || w
== lastfocus
) &&
131 ! screen
->getBlackbox()->doShutdown()) {
135 if (! w
->isNormal()) return;
137 BlackboxWindowList::iterator it
, end
= windowList
.end();
139 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
144 windowList
.erase(it
);
145 clientmenu
->remove(i
);
146 clientmenu
->update();
149 screen
->updateNetizenWindowDel(w
->getClientWindow());
151 BlackboxWindowList::iterator it
= windowList
.begin();
152 const BlackboxWindowList::iterator end
= windowList
.end();
154 for (; it
!= end
; ++it
, ++i
)
155 (*it
)->setWindowNumber(i
);
159 cascade_x
= cascade_y
= 0;
167 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
168 BlackboxWindow
*newfocus
= 0;
170 if (id
== screen
->getCurrentWorkspaceID()) {
171 // The window is on the visible workspace.
173 // if it's a transient, then try to focus its parent
174 if (old_window
&& old_window
->isTransient()) {
175 newfocus
= old_window
->getTransientFor();
178 newfocus
->isIconic() || // do not focus icons
179 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
180 ! newfocus
->setInputFocus())
185 BlackboxWindowList::iterator it
= stackingList
.begin(),
186 end
= stackingList
.end();
187 for (; it
!= end
; ++it
) {
188 BlackboxWindow
*tmp
= *it
;
189 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
190 // we found our new focus target
197 screen
->getBlackbox()->setFocusedWindow(newfocus
);
199 // The window is not on the visible workspace.
201 if (old_window
&& lastfocus
== old_window
) {
202 // The window was the last-focus target, so we need to replace it.
203 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
204 if (! stackingList
.empty())
205 win
= stackingList
.front();
206 setLastFocusedWindow(win
);
212 void Workspace::setFocused(const BlackboxWindow
*w
, bool focused
) {
213 BlackboxWindowList::iterator it
, end
= windowList
.end();
215 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
218 // if its == end, then a window thats not in the windowList
219 // got focused, such as a !isNormal() window.
221 clientmenu
->setItemSelected(i
, focused
);
225 void Workspace::showAll(void) {
226 BlackboxWindowList::iterator it
= stackingList
.begin();
227 const BlackboxWindowList::iterator end
= stackingList
.end();
228 for (; it
!= end
; ++it
) {
229 BlackboxWindow
*bw
= *it
;
235 void Workspace::hideAll(void) {
236 // withdraw in reverse order to minimize the number of Expose events
237 BlackboxWindowList::reverse_iterator it
= stackingList
.rbegin();
238 const BlackboxWindowList::reverse_iterator end
= stackingList
.rend();
240 BlackboxWindow
*bw
= *it
;
241 ++it
; // withdraw removes the current item from the list so we need the next
242 // iterator before that happens
248 void Workspace::removeAll(void) {
249 while (! windowList
.empty())
250 windowList
.front()->iconify();
255 * returns the number of transients for win, plus the number of transients
256 * associated with each transient of win
258 static int countTransients(const BlackboxWindow
* const win
) {
259 int ret
= win
->getTransients().size();
261 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
262 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
263 ret
+= countTransients(*it
);
271 * puts the transients of win into the stack. windows are stacked above
272 * the window before it in the stackvector being iterated, meaning
273 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
276 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
277 StackVector::iterator
&stack
) {
278 if (win
->getTransients().size() == 0) return; // nothing to do
280 // put win's transients in the stack
281 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
282 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
283 *stack
++ = (*it
)->getFrameWindow();
284 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
286 if (! (*it
)->isIconic()) {
287 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
288 wkspc
->stackingList
.remove((*it
));
289 wkspc
->stackingList
.push_front((*it
));
293 // put transients of win's transients in the stack
294 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
295 raiseTransients(*it
, stack
);
300 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
301 StackVector::iterator
&stack
) {
302 if (win
->getTransients().size() == 0) return; // nothing to do
304 // put transients of win's transients in the stack
305 BlackboxWindowList::const_reverse_iterator it
,
306 end
= win
->getTransients().rend();
307 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
308 lowerTransients(*it
, stack
);
311 // put win's transients in the stack
312 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
313 *stack
++ = (*it
)->getFrameWindow();
314 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
316 if (! (*it
)->isIconic()) {
317 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
318 wkspc
->stackingList
.remove((*it
));
319 wkspc
->stackingList
.push_back((*it
));
325 void Workspace::raiseWindow(BlackboxWindow
*w
) {
326 BlackboxWindow
*win
= w
;
328 if (win
->isDesktop()) return;
330 // walk up the transient_for's to the window that is not a transient
331 while (win
->isTransient() && ! win
->isDesktop()) {
332 if (! win
->getTransientFor()) break;
333 win
= win
->getTransientFor();
336 // get the total window count (win and all transients)
337 unsigned int i
= 1 + countTransients(win
);
339 // stack the window with all transients above
340 StackVector
stack_vector(i
);
341 StackVector::iterator stack
= stack_vector
.begin();
343 *(stack
++) = win
->getFrameWindow();
344 screen
->updateNetizenWindowRaise(win
->getClientWindow());
345 if (! (win
->isIconic() || win
->isDesktop())) {
346 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
347 wkspc
->stackingList
.remove(win
);
348 wkspc
->stackingList
.push_front(win
);
351 raiseTransients(win
, stack
);
353 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
357 void Workspace::lowerWindow(BlackboxWindow
*w
) {
358 BlackboxWindow
*win
= w
;
360 // walk up the transient_for's to the window that is not a transient
361 while (win
->isTransient() && ! win
->isDesktop()) {
362 if (! win
->getTransientFor()) break;
363 win
= win
->getTransientFor();
366 // get the total window count (win and all transients)
367 unsigned int i
= 1 + countTransients(win
);
369 // stack the window with all transients above
370 StackVector
stack_vector(i
);
371 StackVector::iterator stack
= stack_vector
.begin();
373 lowerTransients(win
, stack
);
375 *(stack
++) = win
->getFrameWindow();
376 screen
->updateNetizenWindowLower(win
->getClientWindow());
377 if (! (win
->isIconic() || win
->isDesktop())) {
378 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
379 wkspc
->stackingList
.remove(win
);
380 wkspc
->stackingList
.push_back(win
);
383 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
387 void Workspace::reconfigure(void) {
388 clientmenu
->reconfigure();
389 std::for_each(windowList
.begin(), windowList
.end(),
390 std::mem_fun(&BlackboxWindow::reconfigure
));
394 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
395 if (index
< windowList
.size()) {
396 BlackboxWindowList::iterator it
= windowList
.begin();
397 for(; index
> 0; --index
, ++it
); /* increment to index */
405 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
406 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
409 assert(it
!= windowList
.end()); // window must be in list
411 if (it
== windowList
.end())
412 return windowList
.front(); // if we walked off the end, wrap around
418 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
419 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
422 assert(it
!= windowList
.end()); // window must be in list
423 if (it
== windowList
.begin())
424 return windowList
.back(); // if we walked of the front, wrap around
430 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
431 return stackingList
.front();
435 void Workspace::sendWindowList(Netizen
&n
) {
436 BlackboxWindowList::iterator it
= windowList
.begin(),
437 end
= windowList
.end();
438 for(; it
!= end
; ++it
)
439 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
443 unsigned int Workspace::getCount(void) const {
444 return windowList
.size();
448 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
449 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
450 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
451 for (; it
!= end
; ++it
)
452 if ((*it
)->isNormal())
453 stack_order
.push_back(*it
);
457 bool Workspace::isCurrent(void) const {
458 return (id
== screen
->getCurrentWorkspaceID());
462 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
463 return (w
== windowList
.back());
467 void Workspace::setCurrent(void) {
468 screen
->changeWorkspaceID(id
);
472 void Workspace::readName(void) {
473 XAtom::StringVect namesList
;
474 unsigned long numnames
= id
+ 1;
476 // attempt to get from the _NET_WM_DESKTOP_NAMES property
477 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
478 XAtom::utf8
, numnames
, namesList
) &&
479 namesList
.size() > id
) {
480 name
= namesList
[id
];
482 clientmenu
->setLabel(name
);
483 clientmenu
->update();
486 Use a default name. This doesn't actually change the class. That will
487 happen after the setName changes the root property, and that change
488 makes its way back to this function.
490 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
492 assert(tmp
.length() < 32);
493 char default_name
[32];
494 sprintf(default_name
, tmp
.c_str(), id
+ 1);
496 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
501 void Workspace::setName(const string
& new_name
) {
502 // set the _NET_WM_DESKTOP_NAMES property with the new name
503 XAtom::StringVect namesList
;
504 unsigned long numnames
= (unsigned) -1;
505 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
506 XAtom::utf8
, numnames
, namesList
) &&
507 namesList
.size() > id
)
508 namesList
[id
] = new_name
;
510 namesList
.push_back(new_name
);
512 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
513 XAtom::utf8
, namesList
);
518 * Calculate free space available for window placement.
520 typedef std::vector
<Rect
> rectList
;
522 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
525 rectList::const_iterator siter
, end
= spaces
.end();
526 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
527 const Rect
&curr
= *siter
;
529 if(! win
.intersects(curr
)) {
530 result
.push_back(curr
);
534 /* Use an intersection of win and curr to determine the space around
535 * curr that we can use.
537 * NOTE: the spaces calculated can overlap.
542 extra
.setCoords(curr
.left(), curr
.top(),
543 isect
.left() - 1, curr
.bottom());
544 if (extra
.valid()) result
.push_back(extra
);
547 extra
.setCoords(curr
.left(), curr
.top(),
548 curr
.right(), isect
.top() - 1);
549 if (extra
.valid()) result
.push_back(extra
);
552 extra
.setCoords(isect
.right() + 1, curr
.top(),
553 curr
.right(), curr
.bottom());
554 if (extra
.valid()) result
.push_back(extra
);
557 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
558 curr
.right(), curr
.bottom());
559 if (extra
.valid()) result
.push_back(extra
);
565 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
566 if (first
.bottom() == second
.bottom())
567 return first
.right() > second
.right();
568 return first
.bottom() > second
.bottom();
571 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
572 if (first
.y() == second
.y())
573 return first
.right() > second
.right();
574 return first
.y() < second
.y();
577 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
578 if (first
.bottom() == second
.bottom())
579 return first
.x() < second
.x();
580 return first
.bottom() > second
.bottom();
583 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
584 if (first
.y() == second
.y())
585 return first
.x() < second
.x();
586 return first
.y() < second
.y();
589 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
590 if (first
.x() == second
.x())
591 return first
.y() < second
.y();
592 return first
.x() < second
.x();
595 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
596 if (first
.x() == second
.x())
597 return first
.bottom() > second
.bottom();
598 return first
.x() < second
.x();
601 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
602 if (first
.right() == second
.right())
603 return first
.y() < second
.y();
604 return first
.right() > second
.right();
607 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
608 if (first
.right() == second
.right())
609 return first
.bottom() > second
.bottom();
610 return first
.right() > second
.right();
614 bool Workspace::smartPlacement(Rect
& win
) {
617 //initially the entire screen is free
619 if (screen
->isXineramaActive() &&
620 screen
->getBlackbox()->doXineramaPlacement()) {
621 RectList availableAreas
= screen
->allAvailableAreas();
622 RectList::iterator it
, end
= availableAreas
.end();
624 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
625 spaces
.push_back(*it
);
628 spaces
.push_back(screen
->availableArea());
631 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
632 end
= windowList
.end();
634 for (; wit
!= end
; ++wit
) {
635 const BlackboxWindow
* const curr
= *wit
;
637 // watch for shaded windows and full-maxed windows
638 if (curr
->isShaded()) {
639 if (screen
->getPlaceIgnoreShaded()) continue;
640 } else if (curr
->isMaximizedFull()) {
641 if (screen
->getPlaceIgnoreMaximized()) continue;
644 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
645 curr
->frameRect().width() + screen
->getBorderWidth(),
646 curr
->frameRect().height() + screen
->getBorderWidth());
648 spaces
= calcSpace(tmp
, spaces
);
651 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
652 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
653 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
654 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
656 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
658 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
659 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
661 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
664 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
665 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
666 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
668 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
670 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
671 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
673 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
677 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
678 for(; sit
!= spaces_end
; ++sit
) {
679 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
683 if (sit
== spaces_end
)
686 //set new position based on the empty space found
687 const Rect
& where
= *sit
;
691 // adjust the location() based on left/right and top/bottom placement
692 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
693 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
694 win
.setX(where
.right() - win
.width());
695 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
696 win
.setY(where
.bottom() - win
.height());
698 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
699 win
.setY(win
.y() + where
.height() - win
.height());
700 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
701 win
.setX(win
.x() + where
.width() - win
.width());
707 bool Workspace::underMousePlacement(Rect
&win
) {
711 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
712 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
716 if (screen
->isXineramaActive() &&
717 screen
->getBlackbox()->doXineramaPlacement()) {
718 RectList availableAreas
= screen
->allAvailableAreas();
719 RectList::iterator it
, end
= availableAreas
.end();
721 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
722 if (it
->contains(rx
, ry
)) break;
723 assert(it
!= end
); // the mouse isn't inside an area?
727 area
= screen
->availableArea();
729 x
= rx
- win
.width() / 2;
730 y
= ry
- win
.height() / 2;
736 if (x
+ win
.width() > area
.x() + area
.width())
737 x
= area
.x() + area
.width() - win
.width();
738 if (y
+ win
.height() > area
.y() + area
.height())
739 y
= area
.y() + area
.height() - win
.height();
748 bool Workspace::cascadePlacement(Rect
&win
, const int offset
) {
752 if (screen
->isXineramaActive() &&
753 screen
->getBlackbox()->doXineramaPlacement()) {
754 area
= screen
->allAvailableAreas()[cascade_region
];
757 area
= screen
->availableArea();
759 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
760 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
761 cascade_x
= cascade_y
= 0;
763 if (screen
->isXineramaActive() &&
764 screen
->getBlackbox()->doXineramaPlacement()) {
765 // go to the next xinerama region, and use its area
766 if (++cascade_region
>= screen
->allAvailableAreas().size())
768 area
= screen
->allAvailableAreas()[cascade_region
];
773 if (cascade_x
== 0) {
774 cascade_x
= area
.x() + offset
;
775 cascade_y
= area
.y() + offset
;
778 win
.setPos(cascade_x
, cascade_y
);
787 void Workspace::placeWindow(BlackboxWindow
*win
) {
788 Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
791 switch (screen
->getPlacementPolicy()) {
792 case BScreen::RowSmartPlacement
:
793 case BScreen::ColSmartPlacement
:
794 placed
= smartPlacement(new_win
);
796 case BScreen::UnderMousePlacement
:
797 case BScreen::ClickMousePlacement
:
798 placed
= underMousePlacement(new_win
);
800 break; // handled below
804 cascadePlacement(new_win
, (win
->getTitleHeight() +
805 screen
->getBorderWidth() * 2));
807 if (new_win
.right() > screen
->availableArea().right())
808 new_win
.setX(screen
->availableArea().left());
809 if (new_win
.bottom() > screen
->availableArea().bottom())
810 new_win
.setY(screen
->availableArea().top());
812 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());