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
) {
84 if (place
) placeWindow(w
);
86 stackingList
.push_front(w
);
90 w
->setWindowNumber(windowList
.size());
92 windowList
.push_back(w
);
94 clientmenu
->insert(w
->getTitle());
97 screen
->updateNetizenWindowAdd(w
->getClientWindow(), id
);
99 if (id
!= screen
->getCurrentWorkspaceID() &&
100 screen
->doFocusNew()) {
102 not on the focused workspace, so the window is not going to get focus
103 but if the user wants new windows focused, then it should get focus
104 when this workspace does become focused.
110 if (! w
->isDesktop())
117 void Workspace::removeWindow(BlackboxWindow
*w
) {
120 stackingList
.remove(w
);
122 // pass focus to the next appropriate window
123 if ((w
->isFocused() || w
== lastfocus
) &&
124 ! screen
->getBlackbox()->doShutdown()) {
127 // if the window is sticky, then it needs to be removed on all other
130 for (unsigned int i
= 0; i
< screen
->getWorkspaceCount(); ++i
)
132 screen
->getWorkspace(i
)->focusFallback(w
);
136 if (! w
->isNormal()) return;
138 windowList
.remove(w
);
139 clientmenu
->remove(w
->getWindowNumber());
140 clientmenu
->update();
142 screen
->updateNetizenWindowDel(w
->getClientWindow());
144 BlackboxWindowList::iterator it
= windowList
.begin();
145 const BlackboxWindowList::iterator end
= windowList
.end();
147 for (; it
!= end
; ++it
, ++i
)
148 (*it
)->setWindowNumber(i
);
151 cascade_x
= cascade_y
= 0;
159 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
160 BlackboxWindow
*newfocus
= 0;
162 if (id
== screen
->getCurrentWorkspaceID()) {
163 // The window is on the visible workspace.
165 // if it's a transient, then try to focus its parent
166 if (old_window
&& old_window
->isTransient()) {
167 newfocus
= old_window
->getTransientFor();
170 newfocus
->isIconic() || // do not focus icons
171 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
172 ! newfocus
->setInputFocus())
177 BlackboxWindowList::iterator it
= stackingList
.begin(),
178 end
= stackingList
.end();
179 for (; it
!= end
; ++it
) {
180 BlackboxWindow
*tmp
= *it
;
181 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
182 // we found our new focus target
189 screen
->getBlackbox()->setFocusedWindow(newfocus
);
191 // The window is not on the visible workspace.
193 if (old_window
&& lastfocus
== old_window
) {
194 // The window was the last-focus target, so we need to replace it.
195 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
196 if (! stackingList
.empty())
197 win
= stackingList
.front();
198 setLastFocusedWindow(win
);
204 void Workspace::showAll(void) {
205 BlackboxWindowList::iterator it
= stackingList
.begin();
206 const BlackboxWindowList::iterator end
= stackingList
.end();
207 for (; it
!= end
; ++it
) {
208 BlackboxWindow
*bw
= *it
;
215 void Workspace::hideAll(void) {
216 // withdraw in reverse order to minimize the number of Expose events
217 BlackboxWindowList::reverse_iterator it
= stackingList
.rbegin();
218 const BlackboxWindowList::reverse_iterator end
= stackingList
.rend();
220 BlackboxWindow
*bw
= *it
;
221 ++it
; // withdraw removes the current item from the list so we need the next
222 // iterator before that happens
229 void Workspace::removeAll(void) {
230 while (! windowList
.empty())
231 windowList
.front()->iconify();
236 * returns the number of transients for win, plus the number of transients
237 * associated with each transient of win
239 static int countTransients(const BlackboxWindow
* const win
) {
240 int ret
= win
->getTransients().size();
242 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
243 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
244 ret
+= countTransients(*it
);
252 * puts the transients of win into the stack. windows are stacked above
253 * the window before it in the stackvector being iterated, meaning
254 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
257 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
258 StackVector::iterator
&stack
) {
259 if (win
->getTransients().size() == 0) return; // nothing to do
261 // put win's transients in the stack
262 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
263 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
264 *stack
++ = (*it
)->getFrameWindow();
265 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
267 if (! (*it
)->isIconic()) {
268 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
269 wkspc
->stackingList
.remove((*it
));
270 wkspc
->stackingList
.push_front((*it
));
274 // put transients of win's transients in the stack
275 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
276 raiseTransients(*it
, stack
);
281 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
282 StackVector::iterator
&stack
) {
283 if (win
->getTransients().size() == 0) return; // nothing to do
285 // put transients of win's transients in the stack
286 BlackboxWindowList::const_reverse_iterator it
,
287 end
= win
->getTransients().rend();
288 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
289 lowerTransients(*it
, stack
);
292 // put win's transients in the stack
293 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
294 *stack
++ = (*it
)->getFrameWindow();
295 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
297 if (! (*it
)->isIconic()) {
298 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
299 wkspc
->stackingList
.remove((*it
));
300 wkspc
->stackingList
.push_back((*it
));
306 void Workspace::raiseWindow(BlackboxWindow
*w
) {
307 BlackboxWindow
*win
= w
;
309 if (win
->isDesktop()) return;
311 // walk up the transient_for's to the window that is not a transient
312 while (win
->isTransient() && ! win
->isDesktop()) {
313 if (! win
->getTransientFor()) break;
314 win
= win
->getTransientFor();
317 // get the total window count (win and all transients)
318 unsigned int i
= 1 + countTransients(win
);
320 // stack the window with all transients above
321 StackVector
stack_vector(i
);
322 StackVector::iterator stack
= stack_vector
.begin();
324 *(stack
++) = win
->getFrameWindow();
325 screen
->updateNetizenWindowRaise(win
->getClientWindow());
326 if (! (win
->isIconic() || win
->isDesktop())) {
327 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
328 wkspc
->stackingList
.remove(win
);
329 wkspc
->stackingList
.push_front(win
);
332 raiseTransients(win
, stack
);
334 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
338 void Workspace::lowerWindow(BlackboxWindow
*w
) {
339 BlackboxWindow
*win
= w
;
341 // walk up the transient_for's to the window that is not a transient
342 while (win
->isTransient() && ! win
->isDesktop()) {
343 if (! win
->getTransientFor()) break;
344 win
= win
->getTransientFor();
347 // get the total window count (win and all transients)
348 unsigned int i
= 1 + countTransients(win
);
350 // stack the window with all transients above
351 StackVector
stack_vector(i
);
352 StackVector::iterator stack
= stack_vector
.begin();
354 lowerTransients(win
, stack
);
356 *(stack
++) = win
->getFrameWindow();
357 screen
->updateNetizenWindowLower(win
->getClientWindow());
358 if (! (win
->isIconic() || win
->isDesktop())) {
359 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
360 wkspc
->stackingList
.remove(win
);
361 wkspc
->stackingList
.push_back(win
);
364 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
368 void Workspace::reconfigure(void) {
369 clientmenu
->reconfigure();
370 std::for_each(windowList
.begin(), windowList
.end(),
371 std::mem_fun(&BlackboxWindow::reconfigure
));
375 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
376 if (index
< windowList
.size()) {
377 BlackboxWindowList::iterator it
= windowList
.begin();
378 for(; index
> 0; --index
, ++it
); /* increment to index */
386 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
387 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
390 assert(it
!= windowList
.end()); // window must be in list
392 if (it
== windowList
.end())
393 return windowList
.front(); // if we walked off the end, wrap around
399 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
400 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
403 assert(it
!= windowList
.end()); // window must be in list
404 if (it
== windowList
.begin())
405 return windowList
.back(); // if we walked of the front, wrap around
411 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
412 return stackingList
.front();
416 void Workspace::sendWindowList(Netizen
&n
) {
417 BlackboxWindowList::iterator it
= windowList
.begin(),
418 end
= windowList
.end();
419 for(; it
!= end
; ++it
)
420 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
424 unsigned int Workspace::getCount(void) const {
425 return windowList
.size();
429 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
430 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
431 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
432 for (; it
!= end
; ++it
)
433 if ((*it
)->isNormal())
434 stack_order
.push_back(*it
);
438 bool Workspace::isCurrent(void) const {
439 return (id
== screen
->getCurrentWorkspaceID());
443 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
444 return (w
== windowList
.back());
448 void Workspace::setCurrent(void) {
449 screen
->changeWorkspaceID(id
);
453 void Workspace::readName(void) {
454 XAtom::StringVect namesList
;
455 unsigned long numnames
= id
+ 1;
457 // attempt to get from the _NET_WM_DESKTOP_NAMES property
458 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
459 XAtom::utf8
, numnames
, namesList
) &&
460 namesList
.size() > id
) {
461 name
= namesList
[id
];
463 clientmenu
->setLabel(name
);
464 clientmenu
->update();
467 Use a default name. This doesn't actually change the class. That will
468 happen after the setName changes the root property, and that change
469 makes its way back to this function.
471 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
473 assert(tmp
.length() < 32);
474 char default_name
[32];
475 sprintf(default_name
, tmp
.c_str(), id
+ 1);
477 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
482 void Workspace::setName(const string
& new_name
) {
483 // set the _NET_WM_DESKTOP_NAMES property with the new name
484 XAtom::StringVect namesList
;
485 unsigned long numnames
= (unsigned) -1;
486 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
487 XAtom::utf8
, numnames
, namesList
) &&
488 namesList
.size() > id
)
489 namesList
[id
] = new_name
;
491 namesList
.push_back(new_name
);
493 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
494 XAtom::utf8
, namesList
);
499 * Calculate free space available for window placement.
501 typedef std::vector
<Rect
> rectList
;
503 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
506 rectList::const_iterator siter
, end
= spaces
.end();
507 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
508 const Rect
&curr
= *siter
;
510 if(! win
.intersects(curr
)) {
511 result
.push_back(curr
);
515 /* Use an intersection of win and curr to determine the space around
516 * curr that we can use.
518 * NOTE: the spaces calculated can overlap.
523 extra
.setCoords(curr
.left(), curr
.top(),
524 isect
.left() - 1, curr
.bottom());
525 if (extra
.valid()) result
.push_back(extra
);
528 extra
.setCoords(curr
.left(), curr
.top(),
529 curr
.right(), isect
.top() - 1);
530 if (extra
.valid()) result
.push_back(extra
);
533 extra
.setCoords(isect
.right() + 1, curr
.top(),
534 curr
.right(), curr
.bottom());
535 if (extra
.valid()) result
.push_back(extra
);
538 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
539 curr
.right(), curr
.bottom());
540 if (extra
.valid()) result
.push_back(extra
);
546 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
547 if (first
.bottom() == second
.bottom())
548 return first
.right() > second
.right();
549 return first
.bottom() > second
.bottom();
552 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
553 if (first
.y() == second
.y())
554 return first
.right() > second
.right();
555 return first
.y() < second
.y();
558 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
559 if (first
.bottom() == second
.bottom())
560 return first
.x() < second
.x();
561 return first
.bottom() > second
.bottom();
564 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
565 if (first
.y() == second
.y())
566 return first
.x() < second
.x();
567 return first
.y() < second
.y();
570 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
571 if (first
.x() == second
.x())
572 return first
.y() < second
.y();
573 return first
.x() < second
.x();
576 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
577 if (first
.x() == second
.x())
578 return first
.bottom() > second
.bottom();
579 return first
.x() < second
.x();
582 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
583 if (first
.right() == second
.right())
584 return first
.y() < second
.y();
585 return first
.right() > second
.right();
588 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
589 if (first
.right() == second
.right())
590 return first
.bottom() > second
.bottom();
591 return first
.right() > second
.right();
595 bool Workspace::smartPlacement(Rect
& win
) {
598 //initially the entire screen is free
600 if (screen
->isXineramaActive() &&
601 screen
->getBlackbox()->doXineramaPlacement()) {
602 RectList availableAreas
= screen
->allAvailableAreas();
603 RectList::iterator it
, end
= availableAreas
.end();
605 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
606 spaces
.push_back(*it
);
609 spaces
.push_back(screen
->availableArea());
612 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
613 end
= windowList
.end();
615 for (; wit
!= end
; ++wit
) {
616 const BlackboxWindow
* const curr
= *wit
;
618 // watch for shaded windows and full-maxed windows
619 if (curr
->isShaded()) {
620 if (screen
->getPlaceIgnoreShaded()) continue;
621 } else if (curr
->isMaximizedFull()) {
622 if (screen
->getPlaceIgnoreMaximized()) continue;
625 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
626 curr
->frameRect().width() + screen
->getBorderWidth(),
627 curr
->frameRect().height() + screen
->getBorderWidth());
629 spaces
= calcSpace(tmp
, spaces
);
632 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
633 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
634 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
635 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
637 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
639 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
640 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
642 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
645 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
646 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
647 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
649 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
651 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
652 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
654 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
658 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
659 for(; sit
!= spaces_end
; ++sit
) {
660 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
664 if (sit
== spaces_end
)
667 //set new position based on the empty space found
668 const Rect
& where
= *sit
;
672 // adjust the location() based on left/right and top/bottom placement
673 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
674 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
675 win
.setX(where
.right() - win
.width());
676 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
677 win
.setY(where
.bottom() - win
.height());
679 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
680 win
.setY(win
.y() + where
.height() - win
.height());
681 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
682 win
.setX(win
.x() + where
.width() - win
.width());
688 bool Workspace::underMousePlacement(Rect
&win
) {
692 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
693 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
697 if (screen
->isXineramaActive() &&
698 screen
->getBlackbox()->doXineramaPlacement()) {
699 RectList availableAreas
= screen
->allAvailableAreas();
700 RectList::iterator it
, end
= availableAreas
.end();
702 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
703 if (it
->contains(rx
, ry
)) break;
704 assert(it
!= end
); // the mouse isn't inside an area?
708 area
= screen
->availableArea();
710 x
= rx
- win
.width() / 2;
711 y
= ry
- win
.height() / 2;
717 if (x
+ win
.width() > area
.x() + area
.width())
718 x
= area
.x() + area
.width() - win
.width();
719 if (y
+ win
.height() > area
.y() + area
.height())
720 y
= area
.y() + area
.height() - win
.height();
729 bool Workspace::cascadePlacement(Rect
&win
, const int offset
) {
733 if (screen
->isXineramaActive() &&
734 screen
->getBlackbox()->doXineramaPlacement()) {
735 area
= screen
->allAvailableAreas()[cascade_region
];
738 area
= screen
->availableArea();
740 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
741 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
742 cascade_x
= cascade_y
= 0;
744 if (screen
->isXineramaActive() &&
745 screen
->getBlackbox()->doXineramaPlacement()) {
746 // go to the next xinerama region, and use its area
747 if (++cascade_region
>= screen
->allAvailableAreas().size())
749 area
= screen
->allAvailableAreas()[cascade_region
];
754 if (cascade_x
== 0) {
755 cascade_x
= area
.x() + offset
;
756 cascade_y
= area
.y() + offset
;
759 win
.setPos(cascade_x
, cascade_y
);
768 void Workspace::placeWindow(BlackboxWindow
*win
) {
769 Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
772 switch (screen
->getPlacementPolicy()) {
773 case BScreen::RowSmartPlacement
:
774 case BScreen::ColSmartPlacement
:
775 placed
= smartPlacement(new_win
);
777 case BScreen::UnderMousePlacement
:
778 case BScreen::ClickMousePlacement
:
779 placed
= underMousePlacement(new_win
);
781 break; // handled below
785 cascadePlacement(new_win
, (win
->getTitleHeight() +
786 screen
->getBorderWidth() * 2));
788 if (new_win
.right() > screen
->availableArea().right())
789 new_win
.setX(screen
->availableArea().left());
790 if (new_win
.bottom() > screen
->availableArea().bottom())
791 new_win
.setY(screen
->availableArea().top());
793 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());