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 stack_order
.push_back(*it
);
415 bool Workspace::isCurrent(void) const {
416 return (id
== screen
->getCurrentWorkspaceID());
420 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
421 return (w
== windowList
.back());
425 void Workspace::setCurrent(void) {
426 screen
->changeWorkspaceID(id
);
430 void Workspace::readName(void) {
431 XAtom::StringVect namesList
;
432 unsigned long numnames
= id
+ 1;
434 // attempt to get from the _NET_WM_DESKTOP_NAMES property
435 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
436 XAtom::utf8
, numnames
, namesList
) &&
437 namesList
.size() > id
) {
438 name
= namesList
[id
];
440 clientmenu
->setLabel(name
);
441 clientmenu
->update();
444 Use a default name. This doesn't actually change the class. That will
445 happen after the setName changes the root property, and that change
446 makes its way back to this function.
448 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
450 assert(tmp
.length() < 32);
451 char default_name
[32];
452 sprintf(default_name
, tmp
.c_str(), id
+ 1);
454 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
459 void Workspace::setName(const string
& new_name
) {
460 // set the _NET_WM_DESKTOP_NAMES property with the new name
461 XAtom::StringVect namesList
;
462 unsigned long numnames
= (unsigned) -1;
463 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
464 XAtom::utf8
, numnames
, namesList
) &&
465 namesList
.size() > id
)
466 namesList
[id
] = new_name
;
468 namesList
.push_back(new_name
);
470 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
471 XAtom::utf8
, namesList
);
476 * Calculate free space available for window placement.
478 typedef std::vector
<Rect
> rectList
;
480 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
483 rectList::const_iterator siter
, end
= spaces
.end();
484 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
485 const Rect
&curr
= *siter
;
487 if(! win
.intersects(curr
)) {
488 result
.push_back(curr
);
492 /* Use an intersection of win and curr to determine the space around
493 * curr that we can use.
495 * NOTE: the spaces calculated can overlap.
500 extra
.setCoords(curr
.left(), curr
.top(),
501 isect
.left() - 1, curr
.bottom());
502 if (extra
.valid()) result
.push_back(extra
);
505 extra
.setCoords(curr
.left(), curr
.top(),
506 curr
.right(), isect
.top() - 1);
507 if (extra
.valid()) result
.push_back(extra
);
510 extra
.setCoords(isect
.right() + 1, curr
.top(),
511 curr
.right(), curr
.bottom());
512 if (extra
.valid()) result
.push_back(extra
);
515 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
516 curr
.right(), curr
.bottom());
517 if (extra
.valid()) result
.push_back(extra
);
523 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
524 if (first
.bottom() == second
.bottom())
525 return first
.right() > second
.right();
526 return first
.bottom() > second
.bottom();
529 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
530 if (first
.y() == second
.y())
531 return first
.right() > second
.right();
532 return first
.y() < second
.y();
535 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
536 if (first
.bottom() == second
.bottom())
537 return first
.x() < second
.x();
538 return first
.bottom() > second
.bottom();
541 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
542 if (first
.y() == second
.y())
543 return first
.x() < second
.x();
544 return first
.y() < second
.y();
547 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
548 if (first
.x() == second
.x())
549 return first
.y() < second
.y();
550 return first
.x() < second
.x();
553 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
554 if (first
.x() == second
.x())
555 return first
.bottom() > second
.bottom();
556 return first
.x() < second
.x();
559 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
560 if (first
.right() == second
.right())
561 return first
.y() < second
.y();
562 return first
.right() > second
.right();
565 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
566 if (first
.right() == second
.right())
567 return first
.bottom() > second
.bottom();
568 return first
.right() > second
.right();
572 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
574 spaces
.push_back(availableArea
); //initially the entire screen is free
577 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
578 end
= windowList
.end();
580 for (; wit
!= end
; ++wit
) {
581 const BlackboxWindow
* const curr
= *wit
;
583 if (curr
->isShaded() && screen
->getPlaceIgnoreShaded()) continue;
584 if (curr
->isMaximizedFull() && screen
->getPlaceIgnoreMaximized()) continue;
586 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
587 curr
->frameRect().width() + screen
->getBorderWidth(),
588 curr
->frameRect().height() + screen
->getBorderWidth());
590 spaces
= calcSpace(tmp
, spaces
);
593 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
594 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
595 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
596 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
598 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
600 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
601 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
603 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
606 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
607 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
608 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
610 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
612 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
613 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
615 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
619 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
620 for(; sit
!= spaces_end
; ++sit
) {
621 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
625 if (sit
== spaces_end
)
628 //set new position based on the empty space found
629 const Rect
& where
= *sit
;
633 // adjust the location() based on left/right and top/bottom placement
634 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
635 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
636 win
.setX(where
.right() - win
.width());
637 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
638 win
.setY(where
.bottom() - win
.height());
640 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
641 win
.setY(win
.y() + where
.height() - win
.height());
642 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
643 win
.setX(win
.x() + where
.width() - win
.width());
649 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
653 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
654 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
655 x
= rx
- win
.width() / 2;
656 y
= ry
- win
.height() / 2;
658 if (x
< availableArea
.x())
659 x
= availableArea
.x();
660 if (y
< availableArea
.y())
661 y
= availableArea
.y();
662 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
663 x
= availableArea
.x() + availableArea
.width() - win
.width();
664 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
665 y
= availableArea
.y() + availableArea
.height() - win
.height();
674 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
675 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
676 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
677 cascade_x
= cascade_y
= 32;
679 if (cascade_x
== 32) {
680 cascade_x
+= availableArea
.x();
681 cascade_y
+= availableArea
.y();
684 win
.setPos(cascade_x
, cascade_y
);
690 void Workspace::placeWindow(BlackboxWindow
*win
) {
691 Rect
availableArea(screen
->availableArea()),
692 new_win(availableArea
.x(), availableArea
.y(),
693 win
->frameRect().width(), win
->frameRect().height());
696 switch (screen
->getPlacementPolicy()) {
697 case BScreen::RowSmartPlacement
:
698 case BScreen::ColSmartPlacement
:
699 placed
= smartPlacement(new_win
, availableArea
);
701 case BScreen::UnderMousePlacement
:
702 case BScreen::ClickMousePlacement
:
703 placed
= underMousePlacement(new_win
, availableArea
);
705 break; // handled below
708 if (placed
== False
) {
709 cascadePlacement(new_win
, availableArea
);
710 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
711 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
714 if (new_win
.right() > availableArea
.right())
715 new_win
.setX(availableArea
.left());
716 if (new_win
.bottom() > availableArea
.bottom())
717 new_win
.setY(availableArea
.top());
718 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());