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()) {
106 not on the focused workspace, so the window is not going to get focus
107 but if the user wants new windows focused, then it should get focus
108 when this workspace does become focused.
115 if (! w
->isDesktop())
122 void Workspace::removeWindow(BlackboxWindow
*w
, bool sticky
) {
125 stackingList
.remove(w
);
127 // pass focus to the next appropriate window
128 if ((w
->isFocused() || w
== lastfocus
) &&
129 ! screen
->getBlackbox()->doShutdown()) {
133 if (! w
->isNormal()) return;
135 BlackboxWindowList::iterator it
, end
= windowList
.end();
137 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
142 windowList
.erase(it
);
143 clientmenu
->remove(i
);
144 clientmenu
->update();
147 screen
->updateNetizenWindowDel(w
->getClientWindow());
149 BlackboxWindowList::iterator it
= windowList
.begin();
150 const BlackboxWindowList::iterator end
= windowList
.end();
152 for (; it
!= end
; ++it
, ++i
)
153 (*it
)->setWindowNumber(i
);
157 cascade_x
= cascade_y
= 0;
165 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
166 BlackboxWindow
*newfocus
= 0;
168 if (id
== screen
->getCurrentWorkspaceID()) {
169 // The window is on the visible workspace.
171 // if it's a transient, then try to focus its parent
172 if (old_window
&& old_window
->isTransient()) {
173 newfocus
= old_window
->getTransientFor();
176 newfocus
->isIconic() || // do not focus icons
177 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
178 ! newfocus
->setInputFocus())
183 BlackboxWindowList::iterator it
= stackingList
.begin(),
184 end
= stackingList
.end();
185 for (; it
!= end
; ++it
) {
186 BlackboxWindow
*tmp
= *it
;
187 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
188 // we found our new focus target
195 screen
->getBlackbox()->setFocusedWindow(newfocus
);
197 // The window is not on the visible workspace.
199 if (old_window
&& lastfocus
== old_window
) {
200 // The window was the last-focus target, so we need to replace it.
201 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
202 if (! stackingList
.empty())
203 win
= stackingList
.front();
204 setLastFocusedWindow(win
);
210 void Workspace::setFocused(const BlackboxWindow
*w
, bool focused
) {
211 BlackboxWindowList::iterator it
, end
= windowList
.end();
213 for (i
= 0, it
= windowList
.begin(); it
!= end
; ++it
, ++i
)
216 // if its == end, then a window thats not in the windowList
217 // got focused, such as a !isNormal() window.
219 clientmenu
->setItemSelected(i
, focused
);
223 void Workspace::removeAll(void) {
224 while (! windowList
.empty())
225 windowList
.front()->iconify();
228 void Workspace::showAll(void) {
229 BlackboxWindowList::iterator it
= stackingList
.begin();
230 const BlackboxWindowList::iterator end
= stackingList
.end();
231 for (; it
!= end
; ++it
) {
232 BlackboxWindow
*bw
= *it
;
233 // not normal windows cant focus from mouse enters anyways, so we dont
234 // need to unmap/remap them on workspace changes
235 if (! bw
->isStuck() || bw
->isNormal())
241 void Workspace::hideAll(void) {
242 // withdraw in reverse order to minimize the number of Expose events
244 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
246 BlackboxWindowList::iterator it
= lst
.begin();
247 const BlackboxWindowList::iterator end
= lst
.end();
248 for (; it
!= end
; ++it
) {
249 BlackboxWindow
*bw
= *it
;
250 // not normal windows cant focus from mouse enters anyways, so we dont
251 // need to unmap/remap them on workspace changes
252 if (! bw
->isStuck() || bw
->isNormal())
260 * returns the number of transients for win, plus the number of transients
261 * associated with each transient of win
263 static unsigned int countTransients(const BlackboxWindow
* const win
) {
264 BlackboxWindowList transients
= win
->getTransients();
265 if (transients
.empty()) return 0;
267 unsigned int ret
= transients
.size();
268 BlackboxWindowList::const_iterator it
= transients
.begin(),
269 end
= transients
.end();
270 for (; it
!= end
; ++it
)
271 ret
+= countTransients(*it
);
278 * puts the transients of win into the stack. windows are stacked above
279 * the window before it in the stackvector being iterated, meaning
280 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
283 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
284 StackVector::iterator
&stack
) {
285 if (win
->getTransients().empty()) return; // nothing to do
287 // put win's transients in the stack
288 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
289 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
290 BlackboxWindow
*w
= *it
;
291 *stack
++ = w
->getFrameWindow();
292 screen
->updateNetizenWindowRaise(w
->getClientWindow());
294 if (! w
->isIconic()) {
295 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
296 wkspc
->stackingList
.remove(w
);
297 wkspc
->stackingList
.push_front(w
);
301 // put transients of win's transients in the stack
302 for (it
= win
->getTransients().begin(); it
!= end
; ++it
)
303 raiseTransients(*it
, stack
);
307 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
308 StackVector::iterator
&stack
) {
309 if (win
->getTransients().empty()) return; // nothing to do
311 // put transients of win's transients in the stack
312 BlackboxWindowList::const_reverse_iterator it
,
313 end
= win
->getTransients().rend();
314 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
)
315 lowerTransients(*it
, stack
);
317 // put win's transients in the stack
318 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
319 BlackboxWindow
*w
= *it
;
320 *stack
++ = w
->getFrameWindow();
321 screen
->updateNetizenWindowLower(w
->getClientWindow());
323 if (! w
->isIconic()) {
324 Workspace
*wkspc
= screen
->getWorkspace(w
->getWorkspaceNumber());
325 wkspc
->stackingList
.remove(w
);
326 wkspc
->stackingList
.push_back(w
);
332 void Workspace::raiseWindow(BlackboxWindow
*w
) {
333 BlackboxWindow
*win
= w
;
335 if (win
->isDesktop()) return;
337 // walk up the transient_for's to the window that is not a transient
338 while (win
->isTransient() && win
->getTransientFor())
339 win
= win
->getTransientFor();
341 // get the total window count (win and all transients)
342 unsigned int i
= 1 + countTransients(win
);
344 // stack the window with all transients above
345 StackVector
stack_vector(i
);
346 StackVector::iterator stack
= stack_vector
.begin();
348 *(stack
++) = win
->getFrameWindow();
349 screen
->updateNetizenWindowRaise(win
->getClientWindow());
350 if (! (win
->isIconic() || win
->isDesktop())) {
351 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
352 wkspc
->stackingList
.remove(win
);
353 wkspc
->stackingList
.push_front(win
);
356 raiseTransients(win
, stack
);
358 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
362 void Workspace::lowerWindow(BlackboxWindow
*w
) {
363 BlackboxWindow
*win
= w
;
365 // walk up the transient_for's to the window that is not a transient
366 while (win
->isTransient() && win
->getTransientFor())
367 win
= win
->getTransientFor();
369 // get the total window count (win and all transients)
370 unsigned int i
= 1 + countTransients(win
);
372 // stack the window with all transients above
373 StackVector
stack_vector(i
);
374 StackVector::iterator stack
= stack_vector
.begin();
376 lowerTransients(win
, stack
);
378 *(stack
++) = win
->getFrameWindow();
379 screen
->updateNetizenWindowLower(win
->getClientWindow());
380 if (! (win
->isIconic() || win
->isDesktop())) {
381 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
382 wkspc
->stackingList
.remove(win
);
383 wkspc
->stackingList
.push_back(win
);
386 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
390 void Workspace::reconfigure(void) {
391 clientmenu
->reconfigure();
392 std::for_each(windowList
.begin(), windowList
.end(),
393 std::mem_fun(&BlackboxWindow::reconfigure
));
397 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
398 if (index
< windowList
.size()) {
399 BlackboxWindowList::iterator it
= windowList
.begin();
400 while (index
-- > 0) // increment to index
410 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
411 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
414 assert(it
!= windowList
.end()); // window must be in list
416 if (it
== windowList
.end())
417 return windowList
.front(); // if we walked off the end, wrap around
423 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
424 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
427 assert(it
!= windowList
.end()); // window must be in list
428 if (it
== windowList
.begin())
429 return windowList
.back(); // if we walked of the front, wrap around
435 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
436 assert(! stackingList
.empty());
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());