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"
56 #include "Workspace.hh"
57 #include "Windowmenu.hh"
61 Workspace::Workspace(BScreen
*scrn
, unsigned int i
) {
63 xatom
= screen
->getBlackbox()->getXAtom();
65 cascade_x
= cascade_y
= 32;
69 clientmenu
= new Clientmenu(this);
71 lastfocus
= (BlackboxWindow
*) 0;
77 void Workspace::addWindow(BlackboxWindow
*w
, bool place
) {
80 if (place
) placeWindow(w
);
82 stackingList
.push_front(w
);
86 w
->setWindowNumber(windowList
.size());
88 windowList
.push_back(w
);
90 clientmenu
->insert(w
->getTitle());
93 screen
->updateNetizenWindowAdd(w
->getClientWindow(), id
);
95 if (id
!= screen
->getCurrentWorkspaceID() &&
96 screen
->doFocusNew()) {
98 not on the focused workspace, so the window is not going to get focus
99 but if the user wants new windows focused, then it should get focus
100 when this workspace does become focused.
106 if (! w
->isDesktop())
113 void Workspace::removeWindow(BlackboxWindow
*w
) {
116 stackingList
.remove(w
);
118 // pass focus to the next appropriate window
119 if ((w
->isFocused() || w
== lastfocus
) &&
120 ! screen
->getBlackbox()->doShutdown()) {
123 // if the window is sticky, then it needs to be removed on all other
126 for (unsigned int i
= 0; i
< screen
->getWorkspaceCount(); ++i
)
128 screen
->getWorkspace(i
)->focusFallback(w
);
132 if (! w
->isNormal()) return;
134 windowList
.remove(w
);
135 clientmenu
->remove(w
->getWindowNumber());
136 clientmenu
->update();
138 screen
->updateNetizenWindowDel(w
->getClientWindow());
140 BlackboxWindowList::iterator it
= windowList
.begin();
141 const BlackboxWindowList::iterator end
= windowList
.end();
143 for (; it
!= end
; ++it
, ++i
)
144 (*it
)->setWindowNumber(i
);
147 cascade_x
= cascade_y
= 32;
151 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
152 BlackboxWindow
*newfocus
= 0;
154 if (id
== screen
->getCurrentWorkspaceID()) {
155 // The window is on the visible workspace.
157 // if it's a transient, then try to focus its parent
158 if (old_window
&& old_window
->isTransient()) {
159 newfocus
= old_window
->getTransientFor();
162 newfocus
->isIconic() || // do not focus icons
163 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
164 ! newfocus
->setInputFocus())
169 BlackboxWindowList::iterator it
= stackingList
.begin(),
170 end
= stackingList
.end();
171 for (; it
!= end
; ++it
) {
172 BlackboxWindow
*tmp
= *it
;
173 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
174 // we found our new focus target
181 screen
->getBlackbox()->setFocusedWindow(newfocus
);
183 // The window is not on the visible workspace.
185 if (old_window
&& lastfocus
== old_window
) {
186 // The window was the last-focus target, so we need to replace it.
187 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
188 if (! stackingList
.empty())
189 win
= stackingList
.front();
190 setLastFocusedWindow(win
);
196 void Workspace::showAll(void) {
197 BlackboxWindowList::iterator it
= stackingList
.begin();
198 const BlackboxWindowList::iterator end
= stackingList
.end();
199 for (; it
!= end
; ++it
) {
200 BlackboxWindow
*bw
= *it
;
207 void Workspace::hideAll(void) {
208 // withdraw in reverse order to minimize the number of Expose events
209 BlackboxWindowList::reverse_iterator it
= stackingList
.rbegin();
210 const BlackboxWindowList::reverse_iterator end
= stackingList
.rend();
212 BlackboxWindow
*bw
= *it
;
213 ++it
; // withdraw removes the current item from the list so we need the next
214 // iterator before that happens
221 void Workspace::removeAll(void) {
222 while (! windowList
.empty())
223 windowList
.front()->iconify();
228 * returns the number of transients for win, plus the number of transients
229 * associated with each transient of win
231 static int countTransients(const BlackboxWindow
* const win
) {
232 int ret
= win
->getTransients().size();
234 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
235 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
236 ret
+= countTransients(*it
);
244 * puts the transients of win into the stack. windows are stacked above
245 * the window before it in the stackvector being iterated, meaning
246 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
249 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
250 StackVector::iterator
&stack
) {
251 if (win
->getTransients().size() == 0) return; // nothing to do
253 // put win's transients in the stack
254 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
255 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
256 *stack
++ = (*it
)->getFrameWindow();
257 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
259 if (! (*it
)->isIconic()) {
260 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
261 wkspc
->stackingList
.remove((*it
));
262 wkspc
->stackingList
.push_front((*it
));
266 // put transients of win's transients in the stack
267 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
268 raiseTransients(*it
, stack
);
273 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
274 StackVector::iterator
&stack
) {
275 if (win
->getTransients().size() == 0) return; // nothing to do
277 // put transients of win's transients in the stack
278 BlackboxWindowList::const_reverse_iterator it
,
279 end
= win
->getTransients().rend();
280 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
281 lowerTransients(*it
, stack
);
284 // put win's transients in the stack
285 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
286 *stack
++ = (*it
)->getFrameWindow();
287 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
289 if (! (*it
)->isIconic()) {
290 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
291 wkspc
->stackingList
.remove((*it
));
292 wkspc
->stackingList
.push_back((*it
));
298 void Workspace::raiseWindow(BlackboxWindow
*w
) {
299 BlackboxWindow
*win
= w
;
301 if (win
->isDesktop()) return;
303 // walk up the transient_for's to the window that is not a transient
304 while (win
->isTransient() && ! win
->isDesktop()) {
305 if (! win
->getTransientFor()) break;
306 win
= win
->getTransientFor();
309 // get the total window count (win and all transients)
310 unsigned int i
= 1 + countTransients(win
);
312 // stack the window with all transients above
313 StackVector
stack_vector(i
);
314 StackVector::iterator stack
= stack_vector
.begin();
316 *(stack
++) = win
->getFrameWindow();
317 screen
->updateNetizenWindowRaise(win
->getClientWindow());
318 if (! (win
->isIconic() || win
->isDesktop())) {
319 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
320 wkspc
->stackingList
.remove(win
);
321 wkspc
->stackingList
.push_front(win
);
324 raiseTransients(win
, stack
);
326 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
330 void Workspace::lowerWindow(BlackboxWindow
*w
) {
331 BlackboxWindow
*win
= w
;
333 // walk up the transient_for's to the window that is not a transient
334 while (win
->isTransient() && ! win
->isDesktop()) {
335 if (! win
->getTransientFor()) break;
336 win
= win
->getTransientFor();
339 // get the total window count (win and all transients)
340 unsigned int i
= 1 + countTransients(win
);
342 // stack the window with all transients above
343 StackVector
stack_vector(i
);
344 StackVector::iterator stack
= stack_vector
.begin();
346 lowerTransients(win
, stack
);
348 *(stack
++) = win
->getFrameWindow();
349 screen
->updateNetizenWindowLower(win
->getClientWindow());
350 if (! (win
->isIconic() || win
->isDesktop())) {
351 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
352 wkspc
->stackingList
.remove(win
);
353 wkspc
->stackingList
.push_back(win
);
356 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
360 void Workspace::reconfigure(void) {
361 clientmenu
->reconfigure();
362 std::for_each(windowList
.begin(), windowList
.end(),
363 std::mem_fun(&BlackboxWindow::reconfigure
));
367 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
368 if (index
< windowList
.size()) {
369 BlackboxWindowList::iterator it
= windowList
.begin();
370 for(; index
> 0; --index
, ++it
); /* increment to index */
378 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
379 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
382 assert(it
!= windowList
.end()); // window must be in list
384 if (it
== windowList
.end())
385 return windowList
.front(); // if we walked off the end, wrap around
391 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
392 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
395 assert(it
!= windowList
.end()); // window must be in list
396 if (it
== windowList
.begin())
397 return windowList
.back(); // if we walked of the front, wrap around
403 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
404 return stackingList
.front();
408 void Workspace::sendWindowList(Netizen
&n
) {
409 BlackboxWindowList::iterator it
= windowList
.begin(),
410 end
= windowList
.end();
411 for(; it
!= end
; ++it
)
412 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
416 unsigned int Workspace::getCount(void) const {
417 return windowList
.size();
421 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
422 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
423 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
424 for (; it
!= end
; ++it
)
425 if ((*it
)->isNormal())
426 stack_order
.push_back(*it
);
430 bool Workspace::isCurrent(void) const {
431 return (id
== screen
->getCurrentWorkspaceID());
435 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
436 return (w
== windowList
.back());
440 void Workspace::setCurrent(void) {
441 screen
->changeWorkspaceID(id
);
445 void Workspace::readName(void) {
446 XAtom::StringVect namesList
;
447 unsigned long numnames
= id
+ 1;
449 // attempt to get from the _NET_WM_DESKTOP_NAMES property
450 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
451 XAtom::utf8
, numnames
, namesList
) &&
452 namesList
.size() > id
) {
453 name
= namesList
[id
];
455 clientmenu
->setLabel(name
);
456 clientmenu
->update();
459 Use a default name. This doesn't actually change the class. That will
460 happen after the setName changes the root property, and that change
461 makes its way back to this function.
463 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
465 assert(tmp
.length() < 32);
466 char default_name
[32];
467 sprintf(default_name
, tmp
.c_str(), id
+ 1);
469 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
474 void Workspace::setName(const string
& new_name
) {
475 // set the _NET_WM_DESKTOP_NAMES property with the new name
476 XAtom::StringVect namesList
;
477 unsigned long numnames
= (unsigned) -1;
478 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
479 XAtom::utf8
, numnames
, namesList
) &&
480 namesList
.size() > id
)
481 namesList
[id
] = new_name
;
483 namesList
.push_back(new_name
);
485 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
486 XAtom::utf8
, namesList
);
491 * Calculate free space available for window placement.
493 typedef std::vector
<Rect
> rectList
;
495 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
498 rectList::const_iterator siter
, end
= spaces
.end();
499 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
500 const Rect
&curr
= *siter
;
502 if(! win
.intersects(curr
)) {
503 result
.push_back(curr
);
507 /* Use an intersection of win and curr to determine the space around
508 * curr that we can use.
510 * NOTE: the spaces calculated can overlap.
515 extra
.setCoords(curr
.left(), curr
.top(),
516 isect
.left() - 1, curr
.bottom());
517 if (extra
.valid()) result
.push_back(extra
);
520 extra
.setCoords(curr
.left(), curr
.top(),
521 curr
.right(), isect
.top() - 1);
522 if (extra
.valid()) result
.push_back(extra
);
525 extra
.setCoords(isect
.right() + 1, curr
.top(),
526 curr
.right(), curr
.bottom());
527 if (extra
.valid()) result
.push_back(extra
);
530 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
531 curr
.right(), curr
.bottom());
532 if (extra
.valid()) result
.push_back(extra
);
538 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
539 if (first
.bottom() == second
.bottom())
540 return first
.right() > second
.right();
541 return first
.bottom() > second
.bottom();
544 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
545 if (first
.y() == second
.y())
546 return first
.right() > second
.right();
547 return first
.y() < second
.y();
550 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
551 if (first
.bottom() == second
.bottom())
552 return first
.x() < second
.x();
553 return first
.bottom() > second
.bottom();
556 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
557 if (first
.y() == second
.y())
558 return first
.x() < second
.x();
559 return first
.y() < second
.y();
562 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
563 if (first
.x() == second
.x())
564 return first
.y() < second
.y();
565 return first
.x() < second
.x();
568 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
569 if (first
.x() == second
.x())
570 return first
.bottom() > second
.bottom();
571 return first
.x() < second
.x();
574 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
575 if (first
.right() == second
.right())
576 return first
.y() < second
.y();
577 return first
.right() > second
.right();
580 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
581 if (first
.right() == second
.right())
582 return first
.bottom() > second
.bottom();
583 return first
.right() > second
.right();
587 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
589 spaces
.push_back(availableArea
); //initially the entire screen is free
592 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
593 end
= windowList
.end();
595 for (; wit
!= end
; ++wit
) {
596 const BlackboxWindow
* const curr
= *wit
;
598 if (curr
->isShaded() && screen
->getPlaceIgnoreShaded()) continue;
599 if (curr
->isMaximizedFull() && screen
->getPlaceIgnoreMaximized()) continue;
601 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
602 curr
->frameRect().width() + screen
->getBorderWidth(),
603 curr
->frameRect().height() + screen
->getBorderWidth());
605 spaces
= calcSpace(tmp
, spaces
);
608 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
609 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
610 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
611 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
613 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
615 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
616 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
618 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
621 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
622 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
623 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
625 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
627 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
628 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
630 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
634 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
635 for(; sit
!= spaces_end
; ++sit
) {
636 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
640 if (sit
== spaces_end
)
643 //set new position based on the empty space found
644 const Rect
& where
= *sit
;
648 // adjust the location() based on left/right and top/bottom placement
649 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
650 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
651 win
.setX(where
.right() - win
.width());
652 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
653 win
.setY(where
.bottom() - win
.height());
655 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
656 win
.setY(win
.y() + where
.height() - win
.height());
657 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
658 win
.setX(win
.x() + where
.width() - win
.width());
664 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
668 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
669 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
670 x
= rx
- win
.width() / 2;
671 y
= ry
- win
.height() / 2;
673 if (x
< availableArea
.x())
674 x
= availableArea
.x();
675 if (y
< availableArea
.y())
676 y
= availableArea
.y();
677 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
678 x
= availableArea
.x() + availableArea
.width() - win
.width();
679 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
680 y
= availableArea
.y() + availableArea
.height() - win
.height();
689 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
690 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
691 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
692 cascade_x
= cascade_y
= 32;
694 if (cascade_x
== 32) {
695 cascade_x
+= availableArea
.x();
696 cascade_y
+= availableArea
.y();
699 win
.setPos(cascade_x
, cascade_y
);
705 void Workspace::placeWindow(BlackboxWindow
*win
) {
706 Rect
availableArea(screen
->availableArea()),
707 new_win(availableArea
.x(), availableArea
.y(),
708 win
->frameRect().width(), win
->frameRect().height());
711 switch (screen
->getPlacementPolicy()) {
712 case BScreen::RowSmartPlacement
:
713 case BScreen::ColSmartPlacement
:
714 placed
= smartPlacement(new_win
, availableArea
);
716 case BScreen::UnderMousePlacement
:
717 case BScreen::ClickMousePlacement
:
718 placed
= underMousePlacement(new_win
, availableArea
);
720 break; // handled below
723 if (placed
== False
) {
724 cascadePlacement(new_win
, availableArea
);
725 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
726 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
729 if (new_win
.right() > availableArea
.right())
730 new_win
.setX(availableArea
.left());
731 if (new_win
.bottom() > availableArea
.bottom())
732 new_win
.setY(availableArea
.top());
733 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());