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
241 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
243 BlackboxWindowList::iterator it
= lst
.begin();
244 const BlackboxWindowList::iterator end
= lst
.end();
245 for (; it
!= end
; ++it
) {
246 BlackboxWindow
*bw
= *it
;
247 // not normal windows cant focus from mouse enters anyways, so we dont
248 // need to unmap/remap them on workspace changes
249 if (! bw
->isStuck() || bw
->isNormal())
255 void Workspace::removeAll(void) {
256 while (! windowList
.empty())
257 windowList
.front()->iconify();
262 * returns the number of transients for win, plus the number of transients
263 * associated with each transient of win
265 static int countTransients(const BlackboxWindow
* const win
) {
266 int ret
= win
->getTransients().size();
268 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
269 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
270 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().size() == 0) 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 *stack
++ = (*it
)->getFrameWindow();
291 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
293 if (! (*it
)->isIconic()) {
294 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
295 wkspc
->stackingList
.remove((*it
));
296 wkspc
->stackingList
.push_front((*it
));
300 // put transients of win's transients in the stack
301 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
302 raiseTransients(*it
, stack
);
307 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
308 StackVector::iterator
&stack
) {
309 if (win
->getTransients().size() == 0) 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
);
318 // put win's transients in the stack
319 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
320 *stack
++ = (*it
)->getFrameWindow();
321 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
323 if (! (*it
)->isIconic()) {
324 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
325 wkspc
->stackingList
.remove((*it
));
326 wkspc
->stackingList
.push_back((*it
));
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
->isDesktop()) {
339 if (! win
->getTransientFor()) break;
340 win
= win
->getTransientFor();
343 // get the total window count (win and all transients)
344 unsigned int i
= 1 + countTransients(win
);
346 // stack the window with all transients above
347 StackVector
stack_vector(i
);
348 StackVector::iterator stack
= stack_vector
.begin();
350 *(stack
++) = win
->getFrameWindow();
351 screen
->updateNetizenWindowRaise(win
->getClientWindow());
352 if (! (win
->isIconic() || win
->isDesktop())) {
353 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
354 wkspc
->stackingList
.remove(win
);
355 wkspc
->stackingList
.push_front(win
);
358 raiseTransients(win
, stack
);
360 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
364 void Workspace::lowerWindow(BlackboxWindow
*w
) {
365 BlackboxWindow
*win
= w
;
367 // walk up the transient_for's to the window that is not a transient
368 while (win
->isTransient() && ! win
->isDesktop()) {
369 if (! win
->getTransientFor()) break;
370 win
= win
->getTransientFor();
373 // get the total window count (win and all transients)
374 unsigned int i
= 1 + countTransients(win
);
376 // stack the window with all transients above
377 StackVector
stack_vector(i
);
378 StackVector::iterator stack
= stack_vector
.begin();
380 lowerTransients(win
, stack
);
382 *(stack
++) = win
->getFrameWindow();
383 screen
->updateNetizenWindowLower(win
->getClientWindow());
384 if (! (win
->isIconic() || win
->isDesktop())) {
385 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
386 wkspc
->stackingList
.remove(win
);
387 wkspc
->stackingList
.push_back(win
);
390 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
394 void Workspace::reconfigure(void) {
395 clientmenu
->reconfigure();
396 std::for_each(windowList
.begin(), windowList
.end(),
397 std::mem_fun(&BlackboxWindow::reconfigure
));
401 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
402 if (index
< windowList
.size()) {
403 BlackboxWindowList::iterator it
= windowList
.begin();
404 for(; index
> 0; --index
, ++it
); /* increment to index */
412 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
413 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
416 assert(it
!= windowList
.end()); // window must be in list
418 if (it
== windowList
.end())
419 return windowList
.front(); // if we walked off the end, wrap around
425 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
426 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
429 assert(it
!= windowList
.end()); // window must be in list
430 if (it
== windowList
.begin())
431 return windowList
.back(); // if we walked of the front, wrap around
437 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
438 return stackingList
.front();
442 void Workspace::sendWindowList(Netizen
&n
) {
443 BlackboxWindowList::iterator it
= windowList
.begin(),
444 end
= windowList
.end();
445 for(; it
!= end
; ++it
)
446 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
450 unsigned int Workspace::getCount(void) const {
451 return windowList
.size();
455 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
456 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
457 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
458 for (; it
!= end
; ++it
)
459 if ((*it
)->isNormal())
460 stack_order
.push_back(*it
);
464 bool Workspace::isCurrent(void) const {
465 return (id
== screen
->getCurrentWorkspaceID());
469 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
470 return (w
== windowList
.back());
474 void Workspace::setCurrent(void) {
475 screen
->changeWorkspaceID(id
);
479 void Workspace::readName(void) {
480 XAtom::StringVect namesList
;
481 unsigned long numnames
= id
+ 1;
483 // attempt to get from the _NET_WM_DESKTOP_NAMES property
484 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
485 XAtom::utf8
, numnames
, namesList
) &&
486 namesList
.size() > id
) {
487 name
= namesList
[id
];
489 clientmenu
->setLabel(name
);
490 clientmenu
->update();
493 Use a default name. This doesn't actually change the class. That will
494 happen after the setName changes the root property, and that change
495 makes its way back to this function.
497 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
499 assert(tmp
.length() < 32);
500 char default_name
[32];
501 sprintf(default_name
, tmp
.c_str(), id
+ 1);
503 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
508 void Workspace::setName(const string
& new_name
) {
509 // set the _NET_WM_DESKTOP_NAMES property with the new name
510 XAtom::StringVect namesList
;
511 unsigned long numnames
= (unsigned) -1;
512 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
513 XAtom::utf8
, numnames
, namesList
) &&
514 namesList
.size() > id
)
515 namesList
[id
] = new_name
;
517 namesList
.push_back(new_name
);
519 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
520 XAtom::utf8
, namesList
);
525 * Calculate free space available for window placement.
527 typedef std::vector
<Rect
> rectList
;
529 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
532 rectList::const_iterator siter
, end
= spaces
.end();
533 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
534 const Rect
&curr
= *siter
;
536 if(! win
.intersects(curr
)) {
537 result
.push_back(curr
);
541 /* Use an intersection of win and curr to determine the space around
542 * curr that we can use.
544 * NOTE: the spaces calculated can overlap.
549 extra
.setCoords(curr
.left(), curr
.top(),
550 isect
.left() - 1, curr
.bottom());
551 if (extra
.valid()) result
.push_back(extra
);
554 extra
.setCoords(curr
.left(), curr
.top(),
555 curr
.right(), isect
.top() - 1);
556 if (extra
.valid()) result
.push_back(extra
);
559 extra
.setCoords(isect
.right() + 1, curr
.top(),
560 curr
.right(), curr
.bottom());
561 if (extra
.valid()) result
.push_back(extra
);
564 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
565 curr
.right(), curr
.bottom());
566 if (extra
.valid()) result
.push_back(extra
);
572 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
573 if (first
.bottom() == second
.bottom())
574 return first
.right() > second
.right();
575 return first
.bottom() > second
.bottom();
578 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
579 if (first
.y() == second
.y())
580 return first
.right() > second
.right();
581 return first
.y() < second
.y();
584 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
585 if (first
.bottom() == second
.bottom())
586 return first
.x() < second
.x();
587 return first
.bottom() > second
.bottom();
590 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
591 if (first
.y() == second
.y())
592 return first
.x() < second
.x();
593 return first
.y() < second
.y();
596 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
597 if (first
.x() == second
.x())
598 return first
.y() < second
.y();
599 return first
.x() < second
.x();
602 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
603 if (first
.x() == second
.x())
604 return first
.bottom() > second
.bottom();
605 return first
.x() < second
.x();
608 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
609 if (first
.right() == second
.right())
610 return first
.y() < second
.y();
611 return first
.right() > second
.right();
614 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
615 if (first
.right() == second
.right())
616 return first
.bottom() > second
.bottom();
617 return first
.right() > second
.right();
621 bool Workspace::smartPlacement(Rect
& win
) {
624 //initially the entire screen is free
626 if (screen
->isXineramaActive() &&
627 screen
->getBlackbox()->doXineramaPlacement()) {
628 RectList availableAreas
= screen
->allAvailableAreas();
629 RectList::iterator it
, end
= availableAreas
.end();
631 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
632 spaces
.push_back(*it
);
635 spaces
.push_back(screen
->availableArea());
638 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
639 end
= windowList
.end();
641 for (; wit
!= end
; ++wit
) {
642 const BlackboxWindow
* const curr
= *wit
;
644 // watch for shaded windows and full-maxed windows
645 if (curr
->isShaded()) {
646 if (screen
->getPlaceIgnoreShaded()) continue;
647 } else if (curr
->isMaximizedFull()) {
648 if (screen
->getPlaceIgnoreMaximized()) continue;
651 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
652 curr
->frameRect().width() + screen
->getBorderWidth(),
653 curr
->frameRect().height() + screen
->getBorderWidth());
655 spaces
= calcSpace(tmp
, spaces
);
658 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
659 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
660 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
661 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
663 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
665 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
666 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
668 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
671 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
672 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
673 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
675 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
677 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
678 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
680 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
684 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
685 for(; sit
!= spaces_end
; ++sit
) {
686 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
690 if (sit
== spaces_end
)
693 //set new position based on the empty space found
694 const Rect
& where
= *sit
;
698 // adjust the location() based on left/right and top/bottom placement
699 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
700 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
701 win
.setX(where
.right() - win
.width());
702 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
703 win
.setY(where
.bottom() - win
.height());
705 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
706 win
.setY(win
.y() + where
.height() - win
.height());
707 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
708 win
.setX(win
.x() + where
.width() - win
.width());
714 bool Workspace::underMousePlacement(Rect
&win
) {
718 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
719 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
723 if (screen
->isXineramaActive() &&
724 screen
->getBlackbox()->doXineramaPlacement()) {
725 RectList availableAreas
= screen
->allAvailableAreas();
726 RectList::iterator it
, end
= availableAreas
.end();
728 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
729 if (it
->contains(rx
, ry
)) break;
730 assert(it
!= end
); // the mouse isn't inside an area?
734 area
= screen
->availableArea();
736 x
= rx
- win
.width() / 2;
737 y
= ry
- win
.height() / 2;
743 if (x
+ win
.width() > area
.x() + area
.width())
744 x
= area
.x() + area
.width() - win
.width();
745 if (y
+ win
.height() > area
.y() + area
.height())
746 y
= area
.y() + area
.height() - win
.height();
755 bool Workspace::cascadePlacement(Rect
&win
, const int offset
) {
759 if (screen
->isXineramaActive() &&
760 screen
->getBlackbox()->doXineramaPlacement()) {
761 area
= screen
->allAvailableAreas()[cascade_region
];
764 area
= screen
->availableArea();
766 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
767 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
768 cascade_x
= cascade_y
= 0;
770 if (screen
->isXineramaActive() &&
771 screen
->getBlackbox()->doXineramaPlacement()) {
772 // go to the next xinerama region, and use its area
773 if (++cascade_region
>= screen
->allAvailableAreas().size())
775 area
= screen
->allAvailableAreas()[cascade_region
];
780 if (cascade_x
== 0) {
781 cascade_x
= area
.x() + offset
;
782 cascade_y
= area
.y() + offset
;
785 win
.setPos(cascade_x
, cascade_y
);
794 void Workspace::placeWindow(BlackboxWindow
*win
) {
795 Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
798 switch (screen
->getPlacementPolicy()) {
799 case BScreen::RowSmartPlacement
:
800 case BScreen::ColSmartPlacement
:
801 placed
= smartPlacement(new_win
);
803 case BScreen::UnderMousePlacement
:
804 case BScreen::ClickMousePlacement
:
805 placed
= underMousePlacement(new_win
);
807 break; // handled below
811 cascadePlacement(new_win
, (win
->getTitleHeight() +
812 screen
->getBorderWidth() * 2));
814 if (new_win
.right() > screen
->availableArea().right())
815 new_win
.setX(screen
->availableArea().left());
816 if (new_win
.bottom() > screen
->availableArea().bottom())
817 new_win
.setY(screen
->availableArea().top());
819 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());