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 if (curr
->isShaded() && screen
->getPlaceIgnoreShaded()) continue;
619 if (curr
->isMaximizedFull() && screen
->getPlaceIgnoreMaximized()) continue;
621 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
622 curr
->frameRect().width() + screen
->getBorderWidth(),
623 curr
->frameRect().height() + screen
->getBorderWidth());
625 spaces
= calcSpace(tmp
, spaces
);
628 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
629 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
630 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
631 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
633 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
635 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
636 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
638 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
641 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
642 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
643 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
645 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
647 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
648 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
650 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
654 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
655 for(; sit
!= spaces_end
; ++sit
) {
656 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
660 if (sit
== spaces_end
)
663 //set new position based on the empty space found
664 const Rect
& where
= *sit
;
668 // adjust the location() based on left/right and top/bottom placement
669 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
670 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
671 win
.setX(where
.right() - win
.width());
672 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
673 win
.setY(where
.bottom() - win
.height());
675 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
676 win
.setY(win
.y() + where
.height() - win
.height());
677 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
678 win
.setX(win
.x() + where
.width() - win
.width());
684 bool Workspace::underMousePlacement(Rect
&win
) {
688 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
689 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
693 if (screen
->isXineramaActive() &&
694 screen
->getBlackbox()->doXineramaPlacement()) {
695 RectList availableAreas
= screen
->allAvailableAreas();
696 RectList::iterator it
, end
= availableAreas
.end();
698 for (it
= availableAreas
.begin(); it
!= end
; ++it
)
699 if (it
->contains(rx
, ry
)) break;
700 assert(it
!= end
); // the mouse isn't inside an area?
704 area
= screen
->availableArea();
706 x
= rx
- win
.width() / 2;
707 y
= ry
- win
.height() / 2;
713 if (x
+ win
.width() > area
.x() + area
.width())
714 x
= area
.x() + area
.width() - win
.width();
715 if (y
+ win
.height() > area
.y() + area
.height())
716 y
= area
.y() + area
.height() - win
.height();
725 bool Workspace::cascadePlacement(Rect
&win
, const int offset
) {
729 if (screen
->isXineramaActive() &&
730 screen
->getBlackbox()->doXineramaPlacement()) {
731 area
= screen
->allAvailableAreas()[cascade_region
];
734 area
= screen
->availableArea();
736 if ((static_cast<signed>(cascade_x
+ win
.width()) > area
.right() + 1) ||
737 (static_cast<signed>(cascade_y
+ win
.height()) > area
.bottom() + 1)) {
738 cascade_x
= cascade_y
= 0;
740 if (screen
->isXineramaActive() &&
741 screen
->getBlackbox()->doXineramaPlacement()) {
742 // go to the next xinerama region, and use its area
743 if (++cascade_region
>= screen
->allAvailableAreas().size())
745 area
= screen
->allAvailableAreas()[cascade_region
];
750 if (cascade_x
== 0) {
751 cascade_x
= area
.x() + offset
;
752 cascade_y
= area
.y() + offset
;
755 win
.setPos(cascade_x
, cascade_y
);
764 void Workspace::placeWindow(BlackboxWindow
*win
) {
765 Rect
new_win(0, 0, win
->frameRect().width(), win
->frameRect().height());
768 switch (screen
->getPlacementPolicy()) {
769 case BScreen::RowSmartPlacement
:
770 case BScreen::ColSmartPlacement
:
771 placed
= smartPlacement(new_win
);
773 case BScreen::UnderMousePlacement
:
774 case BScreen::ClickMousePlacement
:
775 placed
= underMousePlacement(new_win
);
777 break; // handled below
781 cascadePlacement(new_win
, (win
->getTitleHeight() +
782 screen
->getBorderWidth() * 2));
784 // make sure the placement was valid
785 assert(screen
->availableArea().contains(new_win
));
787 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());