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
);
103 void Workspace::removeWindow(BlackboxWindow
*w
) {
106 stackingList
.remove(w
);
108 // pass focus to the next appropriate window
109 if ((w
->isFocused() || w
== lastfocus
) &&
110 ! screen
->getBlackbox()->doShutdown()) {
113 // if the window is sticky, then it needs to be removed on all other
116 for (unsigned int i
= 0; i
< screen
->getWorkspaceCount(); ++i
)
118 screen
->getWorkspace(i
)->focusFallback(w
);
122 if (! w
->isNormal()) return;
124 windowList
.remove(w
);
125 clientmenu
->remove(w
->getWindowNumber());
126 clientmenu
->update();
128 screen
->updateNetizenWindowDel(w
->getClientWindow());
130 BlackboxWindowList::iterator it
= windowList
.begin();
131 const BlackboxWindowList::iterator end
= windowList
.end();
133 for (; it
!= end
; ++it
, ++i
)
134 (*it
)->setWindowNumber(i
);
137 cascade_x
= cascade_y
= 32;
141 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
142 BlackboxWindow
*newfocus
= 0;
144 if (id
== screen
->getCurrentWorkspaceID()) {
145 // The window is on the visible workspace.
147 // if it's a transient, then try to focus its parent
148 if (old_window
&& old_window
->isTransient()) {
149 newfocus
= old_window
->getTransientFor();
152 newfocus
->isIconic() || // do not focus icons
153 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
154 ! newfocus
->setInputFocus())
159 BlackboxWindowList::iterator it
= stackingList
.begin(),
160 end
= stackingList
.end();
161 for (; it
!= end
; ++it
) {
162 BlackboxWindow
*tmp
= *it
;
163 if (tmp
&& tmp
->isNormal() && tmp
->setInputFocus()) {
164 // we found our new focus target
171 screen
->getBlackbox()->setFocusedWindow(newfocus
);
173 // The window is not on the visible workspace.
175 if (old_window
&& lastfocus
== old_window
) {
176 // The window was the last-focus target, so we need to replace it.
177 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
178 if (! stackingList
.empty())
179 win
= stackingList
.front();
180 setLastFocusedWindow(win
);
186 void Workspace::showAll(void) {
187 std::for_each(stackingList
.begin(), stackingList
.end(),
188 std::mem_fun(&BlackboxWindow::show
));
192 void Workspace::hideAll(void) {
193 // withdraw in reverse order to minimize the number of Expose events
195 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
197 BlackboxWindowList::iterator it
= lst
.begin();
198 const BlackboxWindowList::iterator end
= lst
.end();
199 for (; it
!= end
; ++it
) {
200 BlackboxWindow
*bw
= *it
;
207 void Workspace::removeAll(void) {
208 while (! windowList
.empty())
209 windowList
.front()->iconify();
214 * returns the number of transients for win, plus the number of transients
215 * associated with each transient of win
217 static int countTransients(const BlackboxWindow
* const win
) {
218 int ret
= win
->getTransients().size();
220 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
221 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
222 ret
+= countTransients(*it
);
230 * puts the transients of win into the stack. windows are stacked above
231 * the window before it in the stackvector being iterated, meaning
232 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
235 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
236 StackVector::iterator
&stack
) {
237 if (win
->getTransients().size() == 0) return; // nothing to do
239 // put win's transients in the stack
240 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
241 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
242 *stack
++ = (*it
)->getFrameWindow();
243 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
245 if (! (*it
)->isIconic()) {
246 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
247 wkspc
->stackingList
.remove((*it
));
248 wkspc
->stackingList
.push_front((*it
));
252 // put transients of win's transients in the stack
253 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
254 raiseTransients(*it
, stack
);
259 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
260 StackVector::iterator
&stack
) {
261 if (win
->getTransients().size() == 0) return; // nothing to do
263 // put transients of win's transients in the stack
264 BlackboxWindowList::const_reverse_iterator it
,
265 end
= win
->getTransients().rend();
266 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
267 lowerTransients(*it
, stack
);
270 // put win's transients in the stack
271 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
272 *stack
++ = (*it
)->getFrameWindow();
273 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
275 if (! (*it
)->isIconic()) {
276 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
277 wkspc
->stackingList
.remove((*it
));
278 wkspc
->stackingList
.push_back((*it
));
284 void Workspace::raiseWindow(BlackboxWindow
*w
) {
285 BlackboxWindow
*win
= w
;
287 if (win
->isDesktop()) return;
289 // walk up the transient_for's to the window that is not a transient
290 while (win
->isTransient() && ! win
->isDesktop()) {
291 if (! win
->getTransientFor()) break;
292 win
= win
->getTransientFor();
295 // get the total window count (win and all transients)
296 unsigned int i
= 1 + countTransients(win
);
298 // stack the window with all transients above
299 StackVector
stack_vector(i
);
300 StackVector::iterator stack
= stack_vector
.begin();
302 *(stack
++) = win
->getFrameWindow();
303 screen
->updateNetizenWindowRaise(win
->getClientWindow());
304 if (! (win
->isIconic() || win
->isDesktop())) {
305 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
306 wkspc
->stackingList
.remove(win
);
307 wkspc
->stackingList
.push_front(win
);
310 raiseTransients(win
, stack
);
312 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
316 void Workspace::lowerWindow(BlackboxWindow
*w
) {
317 BlackboxWindow
*win
= w
;
319 // walk up the transient_for's to the window that is not a transient
320 while (win
->isTransient() && ! win
->isDesktop()) {
321 if (! win
->getTransientFor()) break;
322 win
= win
->getTransientFor();
325 // get the total window count (win and all transients)
326 unsigned int i
= 1 + countTransients(win
);
328 // stack the window with all transients above
329 StackVector
stack_vector(i
);
330 StackVector::iterator stack
= stack_vector
.begin();
332 lowerTransients(win
, stack
);
334 *(stack
++) = win
->getFrameWindow();
335 screen
->updateNetizenWindowLower(win
->getClientWindow());
336 if (! (win
->isIconic() || win
->isDesktop())) {
337 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
338 wkspc
->stackingList
.remove(win
);
339 wkspc
->stackingList
.push_back(win
);
342 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
346 void Workspace::reconfigure(void) {
347 clientmenu
->reconfigure();
348 std::for_each(windowList
.begin(), windowList
.end(),
349 std::mem_fun(&BlackboxWindow::reconfigure
));
353 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
354 if (index
< windowList
.size()) {
355 BlackboxWindowList::iterator it
= windowList
.begin();
356 for(; index
> 0; --index
, ++it
); /* increment to index */
364 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
365 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
368 assert(it
!= windowList
.end()); // window must be in list
370 if (it
== windowList
.end())
371 return windowList
.front(); // if we walked off the end, wrap around
377 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
378 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
381 assert(it
!= windowList
.end()); // window must be in list
382 if (it
== windowList
.begin())
383 return windowList
.back(); // if we walked of the front, wrap around
389 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
390 return stackingList
.front();
394 void Workspace::sendWindowList(Netizen
&n
) {
395 BlackboxWindowList::iterator it
= windowList
.begin(),
396 end
= windowList
.end();
397 for(; it
!= end
; ++it
)
398 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
402 unsigned int Workspace::getCount(void) const {
403 return windowList
.size();
407 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
408 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
409 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
410 for (; it
!= end
; ++it
)
411 if ((*it
)->isNormal())
412 stack_order
.push_back(*it
);
416 bool Workspace::isCurrent(void) const {
417 return (id
== screen
->getCurrentWorkspaceID());
421 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
422 return (w
== windowList
.back());
426 void Workspace::setCurrent(void) {
427 screen
->changeWorkspaceID(id
);
431 void Workspace::readName(void) {
432 XAtom::StringVect namesList
;
433 unsigned long numnames
= id
+ 1;
435 // attempt to get from the _NET_WM_DESKTOP_NAMES property
436 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
437 XAtom::utf8
, numnames
, namesList
) &&
438 namesList
.size() > id
) {
439 name
= namesList
[id
];
441 clientmenu
->setLabel(name
);
442 clientmenu
->update();
445 Use a default name. This doesn't actually change the class. That will
446 happen after the setName changes the root property, and that change
447 makes its way back to this function.
449 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
451 assert(tmp
.length() < 32);
452 char default_name
[32];
453 sprintf(default_name
, tmp
.c_str(), id
+ 1);
455 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
460 void Workspace::setName(const string
& new_name
) {
461 // set the _NET_WM_DESKTOP_NAMES property with the new name
462 XAtom::StringVect namesList
;
463 unsigned long numnames
= (unsigned) -1;
464 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
465 XAtom::utf8
, numnames
, namesList
) &&
466 namesList
.size() > id
)
467 namesList
[id
] = new_name
;
469 namesList
.push_back(new_name
);
471 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
472 XAtom::utf8
, namesList
);
477 * Calculate free space available for window placement.
479 typedef std::vector
<Rect
> rectList
;
481 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
484 rectList::const_iterator siter
, end
= spaces
.end();
485 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
486 const Rect
&curr
= *siter
;
488 if(! win
.intersects(curr
)) {
489 result
.push_back(curr
);
493 /* Use an intersection of win and curr to determine the space around
494 * curr that we can use.
496 * NOTE: the spaces calculated can overlap.
501 extra
.setCoords(curr
.left(), curr
.top(),
502 isect
.left() - 1, curr
.bottom());
503 if (extra
.valid()) result
.push_back(extra
);
506 extra
.setCoords(curr
.left(), curr
.top(),
507 curr
.right(), isect
.top() - 1);
508 if (extra
.valid()) result
.push_back(extra
);
511 extra
.setCoords(isect
.right() + 1, curr
.top(),
512 curr
.right(), curr
.bottom());
513 if (extra
.valid()) result
.push_back(extra
);
516 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
517 curr
.right(), curr
.bottom());
518 if (extra
.valid()) result
.push_back(extra
);
524 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
525 if (first
.bottom() == second
.bottom())
526 return first
.right() > second
.right();
527 return first
.bottom() > second
.bottom();
530 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
531 if (first
.y() == second
.y())
532 return first
.right() > second
.right();
533 return first
.y() < second
.y();
536 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
537 if (first
.bottom() == second
.bottom())
538 return first
.x() < second
.x();
539 return first
.bottom() > second
.bottom();
542 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
543 if (first
.y() == second
.y())
544 return first
.x() < second
.x();
545 return first
.y() < second
.y();
548 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
549 if (first
.x() == second
.x())
550 return first
.y() < second
.y();
551 return first
.x() < second
.x();
554 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
555 if (first
.x() == second
.x())
556 return first
.bottom() > second
.bottom();
557 return first
.x() < second
.x();
560 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
561 if (first
.right() == second
.right())
562 return first
.y() < second
.y();
563 return first
.right() > second
.right();
566 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
567 if (first
.right() == second
.right())
568 return first
.bottom() > second
.bottom();
569 return first
.right() > second
.right();
573 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
575 spaces
.push_back(availableArea
); //initially the entire screen is free
578 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
579 end
= windowList
.end();
581 for (; wit
!= end
; ++wit
) {
582 const BlackboxWindow
* const curr
= *wit
;
584 if (curr
->isShaded() && screen
->getPlaceIgnoreShaded()) continue;
585 if (curr
->isMaximizedFull() && screen
->getPlaceIgnoreMaximized()) continue;
587 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
588 curr
->frameRect().width() + screen
->getBorderWidth(),
589 curr
->frameRect().height() + screen
->getBorderWidth());
591 spaces
= calcSpace(tmp
, spaces
);
594 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
595 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
596 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
597 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
599 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
601 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
602 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
604 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
607 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
608 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
609 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
611 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
613 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
614 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
616 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
620 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
621 for(; sit
!= spaces_end
; ++sit
) {
622 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
626 if (sit
== spaces_end
)
629 //set new position based on the empty space found
630 const Rect
& where
= *sit
;
634 // adjust the location() based on left/right and top/bottom placement
635 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
636 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
637 win
.setX(where
.right() - win
.width());
638 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
639 win
.setY(where
.bottom() - win
.height());
641 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
642 win
.setY(win
.y() + where
.height() - win
.height());
643 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
644 win
.setX(win
.x() + where
.width() - win
.width());
650 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
654 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
655 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
656 x
= rx
- win
.width() / 2;
657 y
= ry
- win
.height() / 2;
659 if (x
< availableArea
.x())
660 x
= availableArea
.x();
661 if (y
< availableArea
.y())
662 y
= availableArea
.y();
663 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
664 x
= availableArea
.x() + availableArea
.width() - win
.width();
665 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
666 y
= availableArea
.y() + availableArea
.height() - win
.height();
675 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
676 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
677 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
678 cascade_x
= cascade_y
= 32;
680 if (cascade_x
== 32) {
681 cascade_x
+= availableArea
.x();
682 cascade_y
+= availableArea
.y();
685 win
.setPos(cascade_x
, cascade_y
);
691 void Workspace::placeWindow(BlackboxWindow
*win
) {
692 Rect
availableArea(screen
->availableArea()),
693 new_win(availableArea
.x(), availableArea
.y(),
694 win
->frameRect().width(), win
->frameRect().height());
697 switch (screen
->getPlacementPolicy()) {
698 case BScreen::RowSmartPlacement
:
699 case BScreen::ColSmartPlacement
:
700 placed
= smartPlacement(new_win
, availableArea
);
702 case BScreen::UnderMousePlacement
:
703 case BScreen::ClickMousePlacement
:
704 placed
= underMousePlacement(new_win
, availableArea
);
706 break; // handled below
709 if (placed
== False
) {
710 cascadePlacement(new_win
, availableArea
);
711 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
712 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
715 if (new_win
.right() > availableArea
.right())
716 new_win
.setX(availableArea
.left());
717 if (new_win
.bottom() > availableArea
.bottom())
718 new_win
.setY(availableArea
.top());
719 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());