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
;
230 // not normal windows cant focus from mouse enters anyways, so we dont
231 // need to unmap/remap them on workspace changes
232 if (! bw
->isStuck() || bw
->isNormal())
238 void Workspace::hideAll(void) {
239 // withdraw in reverse order to minimize the number of Expose events
240 BlackboxWindowList::reverse_iterator it
= stackingList
.rbegin();
241 const BlackboxWindowList::reverse_iterator end
= stackingList
.rend();
243 BlackboxWindow
*bw
= *it
;
244 ++it
; // withdraw removes the current item from the list so we need the next
245 // iterator before that happens
246 // not normal windows cant focus from mouse enters anyways, so we dont
247 // need to unmap/remap them on workspace changes
248 if (! bw
->isStuck() || bw
->isNormal())
254 void Workspace::removeAll(void) {
255 while (! windowList
.empty())
256 windowList
.front()->iconify();
261 * returns the number of transients for win, plus the number of transients
262 * associated with each transient of win
264 static int countTransients(const BlackboxWindow
* const win
) {
265 int ret
= win
->getTransients().size();
267 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
268 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
269 ret
+= countTransients(*it
);
277 * puts the transients of win into the stack. windows are stacked above
278 * the window before it in the stackvector being iterated, meaning
279 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
282 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
283 StackVector::iterator
&stack
) {
284 if (win
->getTransients().size() == 0) return; // nothing to do
286 // put win's transients in the stack
287 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
288 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
289 *stack
++ = (*it
)->getFrameWindow();
290 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
292 if (! (*it
)->isIconic()) {
293 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
294 wkspc
->stackingList
.remove((*it
));
295 wkspc
->stackingList
.push_front((*it
));
299 // put transients of win's transients in the stack
300 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
301 raiseTransients(*it
, stack
);
306 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
307 StackVector::iterator
&stack
) {
308 if (win
->getTransients().size() == 0) return; // nothing to do
310 // put transients of win's transients in the stack
311 BlackboxWindowList::const_reverse_iterator it
,
312 end
= win
->getTransients().rend();
313 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
314 lowerTransients(*it
, stack
);
317 // put win's transients in the stack
318 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
319 *stack
++ = (*it
)->getFrameWindow();
320 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
322 if (! (*it
)->isIconic()) {
323 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
324 wkspc
->stackingList
.remove((*it
));
325 wkspc
->stackingList
.push_back((*it
));
331 void Workspace::raiseWindow(BlackboxWindow
*w
) {
332 BlackboxWindow
*win
= w
;
334 if (win
->isDesktop()) return;
336 // walk up the transient_for's to the window that is not a transient
337 while (win
->isTransient() && ! win
->isDesktop()) {
338 if (! win
->getTransientFor()) break;
339 win
= win
->getTransientFor();
342 // get the total window count (win and all transients)
343 unsigned int i
= 1 + countTransients(win
);
345 // stack the window with all transients above
346 StackVector
stack_vector(i
);
347 StackVector::iterator stack
= stack_vector
.begin();
349 *(stack
++) = win
->getFrameWindow();
350 screen
->updateNetizenWindowRaise(win
->getClientWindow());
351 if (! (win
->isIconic() || win
->isDesktop())) {
352 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
353 wkspc
->stackingList
.remove(win
);
354 wkspc
->stackingList
.push_front(win
);
357 raiseTransients(win
, stack
);
359 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
363 void Workspace::lowerWindow(BlackboxWindow
*w
) {
364 BlackboxWindow
*win
= w
;
366 // walk up the transient_for's to the window that is not a transient
367 while (win
->isTransient() && ! win
->isDesktop()) {
368 if (! win
->getTransientFor()) break;
369 win
= win
->getTransientFor();
372 // get the total window count (win and all transients)
373 unsigned int i
= 1 + countTransients(win
);
375 // stack the window with all transients above
376 StackVector
stack_vector(i
);
377 StackVector::iterator stack
= stack_vector
.begin();
379 lowerTransients(win
, stack
);
381 *(stack
++) = win
->getFrameWindow();
382 screen
->updateNetizenWindowLower(win
->getClientWindow());
383 if (! (win
->isIconic() || win
->isDesktop())) {
384 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
385 wkspc
->stackingList
.remove(win
);
386 wkspc
->stackingList
.push_back(win
);
389 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
393 void Workspace::reconfigure(void) {
394 clientmenu
->reconfigure();
395 std::for_each(windowList
.begin(), windowList
.end(),
396 std::mem_fun(&BlackboxWindow::reconfigure
));
400 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
401 if (index
< windowList
.size()) {
402 BlackboxWindowList::iterator it
= windowList
.begin();
403 for(; index
> 0; --index
, ++it
); /* increment to index */
411 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
412 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
415 assert(it
!= windowList
.end()); // window must be in list
417 if (it
== windowList
.end())
418 return windowList
.front(); // if we walked off the end, wrap around
424 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
425 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
428 assert(it
!= windowList
.end()); // window must be in list
429 if (it
== windowList
.begin())
430 return windowList
.back(); // if we walked of the front, wrap around
436 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
437 return stackingList
.front();
441 void Workspace::sendWindowList(Netizen
&n
) {
442 BlackboxWindowList::iterator it
= windowList
.begin(),
443 end
= windowList
.end();
444 for(; it
!= end
; ++it
)
445 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
449 unsigned int Workspace::getCount(void) const {
450 return windowList
.size();
454 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
455 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
456 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
457 for (; it
!= end
; ++it
)
458 if ((*it
)->isNormal())
459 stack_order
.push_back(*it
);
463 bool Workspace::isCurrent(void) const {
464 return (id
== screen
->getCurrentWorkspaceID());
468 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
469 return (w
== windowList
.back());
473 void Workspace::setCurrent(void) {
474 screen
->changeWorkspaceID(id
);
478 void Workspace::readName(void) {
479 XAtom::StringVect namesList
;
480 unsigned long numnames
= id
+ 1;
482 // attempt to get from the _NET_WM_DESKTOP_NAMES property
483 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
484 XAtom::utf8
, numnames
, namesList
) &&
485 namesList
.size() > id
) {
486 name
= namesList
[id
];
488 clientmenu
->setLabel(name
);
489 clientmenu
->update();
492 Use a default name. This doesn't actually change the class. That will
493 happen after the setName changes the root property, and that change
494 makes its way back to this function.
496 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
498 assert(tmp
.length() < 32);
499 char default_name
[32];
500 sprintf(default_name
, tmp
.c_str(), id
+ 1);
502 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
507 void Workspace::setName(const string
& new_name
) {
508 // set the _NET_WM_DESKTOP_NAMES property with the new name
509 XAtom::StringVect namesList
;
510 unsigned long numnames
= (unsigned) -1;
511 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
512 XAtom::utf8
, numnames
, namesList
) &&
513 namesList
.size() > id
)
514 namesList
[id
] = new_name
;
516 namesList
.push_back(new_name
);
518 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
519 XAtom::utf8
, namesList
);
524 * Calculate free space available for window placement.
526 typedef std::vector
<Rect
> rectList
;
528 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
531 rectList::const_iterator siter
, end
= spaces
.end();
532 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
533 const Rect
&curr
= *siter
;
535 if(! win
.intersects(curr
)) {
536 result
.push_back(curr
);
540 /* Use an intersection of win and curr to determine the space around
541 * curr that we can use.
543 * NOTE: the spaces calculated can overlap.
548 extra
.setCoords(curr
.left(), curr
.top(),
549 isect
.left() - 1, curr
.bottom());
550 if (extra
.valid()) result
.push_back(extra
);
553 extra
.setCoords(curr
.left(), curr
.top(),
554 curr
.right(), isect
.top() - 1);
555 if (extra
.valid()) result
.push_back(extra
);
558 extra
.setCoords(isect
.right() + 1, curr
.top(),
559 curr
.right(), curr
.bottom());
560 if (extra
.valid()) result
.push_back(extra
);
563 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
564 curr
.right(), curr
.bottom());
565 if (extra
.valid()) result
.push_back(extra
);
571 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
572 if (first
.bottom() == second
.bottom())
573 return first
.right() > second
.right();
574 return first
.bottom() > second
.bottom();
577 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
578 if (first
.y() == second
.y())
579 return first
.right() > second
.right();
580 return first
.y() < second
.y();
583 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
584 if (first
.bottom() == second
.bottom())
585 return first
.x() < second
.x();
586 return first
.bottom() > second
.bottom();
589 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
590 if (first
.y() == second
.y())
591 return first
.x() < second
.x();
592 return first
.y() < second
.y();
595 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
596 if (first
.x() == second
.x())
597 return first
.y() < second
.y();
598 return first
.x() < second
.x();
601 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
602 if (first
.x() == second
.x())
603 return first
.bottom() > second
.bottom();
604 return first
.x() < second
.x();
607 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
608 if (first
.right() == second
.right())
609 return first
.y() < second
.y();
610 return first
.right() > second
.right();
613 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
614 if (first
.right() == second
.right())
615 return first
.bottom() > second
.bottom();
616 return first
.right() > second
.right();
620 bool Workspace::smartPlacement(Rect
& win
) {
623 //initially the entire screen is free
625 if (screen
->isXineramaActive() &&
626 screen
->getBlackbox()->doXineramaPlacement()) {
627 RectList availableAreas
= screen
->allAvailableAreas();
628 RectList::iterator it
, end
= availableAreas
.end();
630 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
631 spaces
.push_back(*it
);
634 spaces
.push_back(screen
->availableArea());
637 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
638 end
= windowList
.end();
640 for (; wit
!= end
; ++wit
) {
641 const BlackboxWindow
* const curr
= *wit
;
643 // watch for shaded windows and full-maxed windows
644 if (curr
->isShaded()) {
645 if (screen
->getPlaceIgnoreShaded()) continue;
646 } else if (curr
->isMaximizedFull()) {
647 if (screen
->getPlaceIgnoreMaximized()) continue;
650 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
651 curr
->frameRect().width() + screen
->getBorderWidth(),
652 curr
->frameRect().height() + screen
->getBorderWidth());
654 spaces
= calcSpace(tmp
, spaces
);
657 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
658 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
659 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
660 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
662 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
664 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
665 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
667 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
670 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
671 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
672 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
674 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
676 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
677 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
679 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
683 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
684 for(; sit
!= spaces_end
; ++sit
) {
685 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
689 if (sit
== spaces_end
)
692 //set new position based on the empty space found
693 const Rect
& where
= *sit
;
697 // adjust the location() based on left/right and top/bottom placement
698 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
699 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
700 win
.setX(where
.right() - win
.width());
701 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
702 win
.setY(where
.bottom() - win
.height());
704 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
705 win
.setY(win
.y() + where
.height() - win
.height());
706 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
707 win
.setX(win
.x() + where
.width() - win
.width());
713 bool Workspace::underMousePlacement(Rect
&win
) {
717 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
718 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
722 if (screen
->isXineramaActive() &&
723 screen
->getBlackbox()->doXineramaPlacement()) {
724 RectList availableAreas
= screen
->allAvailableAreas();
725 RectList::iterator it
, end
= availableAreas
.end();
727 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
728 if (it
->contains(rx
, ry
)) break;
729 assert(it
!= end
); // the mouse isn't inside an area?
733 area
= screen
->availableArea();
735 x
= rx
- win
.width() / 2;
736 y
= ry
- win
.height() / 2;
742 if (x
+ win
.width() > area
.x() + area
.width())
743 x
= area
.x() + area
.width() - win
.width();
744 if (y
+ win
.height() > area
.y() + area
.height())
745 y
= area
.y() + area
.height() - win
.height();
754 bool Workspace::cascadePlacement(Rect
&win
, const int offset
) {
758 if (screen
->isXineramaActive() &&
759 screen
->getBlackbox()->doXineramaPlacement()) {
760 area
= screen
->allAvailableAreas()[cascade_region
];
763 area
= screen
->availableArea();
765 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
766 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
767 cascade_x
= cascade_y
= 0;
769 if (screen
->isXineramaActive() &&
770 screen
->getBlackbox()->doXineramaPlacement()) {
771 // go to the next xinerama region, and use its area
772 if (++cascade_region
>= screen
->allAvailableAreas().size())
774 area
= screen
->allAvailableAreas()[cascade_region
];
779 if (cascade_x
== 0) {
780 cascade_x
= area
.x() + offset
;
781 cascade_y
= area
.y() + offset
;
784 win
.setPos(cascade_x
, cascade_y
);
793 void Workspace::placeWindow(BlackboxWindow
*win
) {
794 Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
797 switch (screen
->getPlacementPolicy()) {
798 case BScreen::RowSmartPlacement
:
799 case BScreen::ColSmartPlacement
:
800 placed
= smartPlacement(new_win
);
802 case BScreen::UnderMousePlacement
:
803 case BScreen::ClickMousePlacement
:
804 placed
= underMousePlacement(new_win
);
806 break; // handled below
810 cascadePlacement(new_win
, (win
->getTitleHeight() +
811 screen
->getBorderWidth() * 2));
813 if (new_win
.right() > screen
->availableArea().right())
814 new_win
.setX(screen
->availableArea().left());
815 if (new_win
.bottom() > screen
->availableArea().bottom())
816 new_win
.setY(screen
->availableArea().top());
818 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());