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
);
83 w
->setWindowNumber(windowList
.size());
85 stackingList
.push_front(w
);
86 windowList
.push_back(w
);
88 clientmenu
->insert(w
->getTitle());
91 screen
->updateNetizenWindowAdd(w
->getClientWindow(), id
);
97 unsigned int Workspace::removeWindow(BlackboxWindow
*w
) {
100 stackingList
.remove(w
);
102 // pass focus to the next appropriate window
103 if ((w
->isFocused() || w
== lastfocus
) &&
104 ! screen
->getBlackbox()->doShutdown()) {
107 // if the window is sticky, then it needs to be removed on all other
110 for (unsigned int i
= 0; i
< screen
->getWorkspaceCount(); ++i
)
112 screen
->getWorkspace(i
)->focusFallback(w
);
116 windowList
.remove(w
);
117 clientmenu
->remove(w
->getWindowNumber());
118 clientmenu
->update();
120 screen
->updateNetizenWindowDel(w
->getClientWindow());
122 BlackboxWindowList::iterator it
= windowList
.begin();
123 const BlackboxWindowList::iterator end
= windowList
.end();
125 for (; it
!= end
; ++it
, ++i
)
126 (*it
)->setWindowNumber(i
);
129 cascade_x
= cascade_y
= 32;
135 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
136 BlackboxWindow
*newfocus
= 0;
138 if (id
== screen
->getCurrentWorkspaceID()) {
139 // The window is on the visible workspace.
141 // if it's a transient, then try to focus its parent
142 if (old_window
&& old_window
->isTransient()) {
143 newfocus
= old_window
->getTransientFor();
146 newfocus
->isIconic() || // do not focus icons
147 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
148 ! newfocus
->setInputFocus())
153 BlackboxWindowList::iterator it
= stackingList
.begin(),
154 end
= stackingList
.end();
155 for (; it
!= end
; ++it
) {
156 BlackboxWindow
*tmp
= *it
;
157 if (! (tmp
->windowType() == BlackboxWindow::Type_Dialog
||
158 tmp
->windowType() == BlackboxWindow::Type_Normal
))
159 continue; // don't fallback to special windows
160 if (tmp
&& tmp
->setInputFocus()) {
161 // we found our new focus target
168 screen
->getBlackbox()->setFocusedWindow(newfocus
);
170 // The window is not on the visible workspace.
172 if (old_window
&& lastfocus
== old_window
) {
173 // The window was the last-focus target, so we need to replace it.
174 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
175 if (! stackingList
.empty())
176 win
= stackingList
.front();
177 setLastFocusedWindow(win
);
183 void Workspace::showAll(void) {
184 std::for_each(stackingList
.begin(), stackingList
.end(),
185 std::mem_fun(&BlackboxWindow::show
));
189 void Workspace::hideAll(void) {
190 // withdraw in reverse order to minimize the number of Expose events
192 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
194 BlackboxWindowList::iterator it
= lst
.begin();
195 const BlackboxWindowList::iterator end
= lst
.end();
196 for (; it
!= end
; ++it
) {
197 BlackboxWindow
*bw
= *it
;
204 void Workspace::removeAll(void) {
205 while (! windowList
.empty())
206 windowList
.front()->iconify();
211 * returns the number of transients for win, plus the number of transients
212 * associated with each transient of win
214 static int countTransients(const BlackboxWindow
* const win
) {
215 int ret
= win
->getTransients().size();
217 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
218 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
219 ret
+= countTransients(*it
);
227 * puts the transients of win into the stack. windows are stacked above
228 * the window before it in the stackvector being iterated, meaning
229 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
232 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
233 StackVector::iterator
&stack
) {
234 if (win
->getTransients().size() == 0) return; // nothing to do
236 // put win's transients in the stack
237 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
238 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
239 *stack
++ = (*it
)->getFrameWindow();
240 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
242 if (! (*it
)->isIconic()) {
243 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
244 wkspc
->stackingList
.remove((*it
));
245 wkspc
->stackingList
.push_front((*it
));
249 // put transients of win's transients in the stack
250 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
251 raiseTransients(*it
, stack
);
256 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
257 StackVector::iterator
&stack
) {
258 if (win
->getTransients().size() == 0) return; // nothing to do
260 // put transients of win's transients in the stack
261 BlackboxWindowList::const_reverse_iterator it
,
262 end
= win
->getTransients().rend();
263 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
264 lowerTransients(*it
, stack
);
267 // put win's transients in the stack
268 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
269 *stack
++ = (*it
)->getFrameWindow();
270 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
272 if (! (*it
)->isIconic()) {
273 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
274 wkspc
->stackingList
.remove((*it
));
275 wkspc
->stackingList
.push_back((*it
));
281 void Workspace::raiseWindow(BlackboxWindow
*w
) {
282 BlackboxWindow
*win
= w
;
284 // walk up the transient_for's to the window that is not a transient
285 while (win
->isTransient()) {
286 if (! win
->getTransientFor()) break;
287 win
= win
->getTransientFor();
290 // get the total window count (win and all transients)
291 unsigned int i
= 1 + countTransients(win
);
293 // stack the window with all transients above
294 StackVector
stack_vector(i
);
295 StackVector::iterator stack
= stack_vector
.begin();
297 *(stack
++) = win
->getFrameWindow();
298 screen
->updateNetizenWindowRaise(win
->getClientWindow());
299 if (! win
->isIconic()) {
300 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
301 wkspc
->stackingList
.remove(win
);
302 wkspc
->stackingList
.push_front(win
);
305 raiseTransients(win
, stack
);
307 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
311 void Workspace::lowerWindow(BlackboxWindow
*w
) {
312 BlackboxWindow
*win
= w
;
314 // walk up the transient_for's to the window that is not a transient
315 while (win
->isTransient()) {
316 if (! win
->getTransientFor()) break;
317 win
= win
->getTransientFor();
320 // get the total window count (win and all transients)
321 unsigned int i
= 1 + countTransients(win
);
323 // stack the window with all transients above
324 StackVector
stack_vector(i
);
325 StackVector::iterator stack
= stack_vector
.begin();
327 lowerTransients(win
, stack
);
329 *(stack
++) = win
->getFrameWindow();
330 screen
->updateNetizenWindowLower(win
->getClientWindow());
331 if (! win
->isIconic()) {
332 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
333 wkspc
->stackingList
.remove(win
);
334 wkspc
->stackingList
.push_back(win
);
337 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
341 void Workspace::reconfigure(void) {
342 clientmenu
->reconfigure();
343 std::for_each(windowList
.begin(), windowList
.end(),
344 std::mem_fun(&BlackboxWindow::reconfigure
));
348 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
349 if (index
< windowList
.size()) {
350 BlackboxWindowList::iterator it
= windowList
.begin();
351 for(; index
> 0; --index
, ++it
); /* increment to index */
359 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
360 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
363 assert(it
!= windowList
.end()); // window must be in list
365 if (it
== windowList
.end())
366 return windowList
.front(); // if we walked off the end, wrap around
372 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
373 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
376 assert(it
!= windowList
.end()); // window must be in list
377 if (it
== windowList
.begin())
378 return windowList
.back(); // if we walked of the front, wrap around
384 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
385 return stackingList
.front();
389 void Workspace::sendWindowList(Netizen
&n
) {
390 BlackboxWindowList::iterator it
= windowList
.begin(),
391 end
= windowList
.end();
392 for(; it
!= end
; ++it
)
393 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
397 unsigned int Workspace::getCount(void) const {
398 return windowList
.size();
402 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
403 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
404 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
405 for (; it
!= end
; ++it
)
406 stack_order
.push_back(*it
);
410 bool Workspace::isCurrent(void) const {
411 return (id
== screen
->getCurrentWorkspaceID());
415 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
416 return (w
== windowList
.back());
420 void Workspace::setCurrent(void) {
421 screen
->changeWorkspaceID(id
);
425 void Workspace::readName(void) {
426 XAtom::StringVect namesList
;
427 unsigned long numnames
= id
+ 1;
429 // attempt to get from the _NET_WM_DESKTOP_NAMES property
430 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
431 XAtom::utf8
, numnames
, namesList
) &&
432 namesList
.size() > id
) {
433 name
= namesList
[id
];
435 clientmenu
->setLabel(name
);
436 clientmenu
->update();
439 Use a default name. This doesn't actually change the class. That will
440 happen after the setName changes the root property, and that change
441 makes its way back to this function.
443 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
445 assert(tmp
.length() < 32);
446 char default_name
[32];
447 sprintf(default_name
, tmp
.c_str(), id
+ 1);
449 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
454 void Workspace::setName(const string
& new_name
) {
455 // set the _NET_WM_DESKTOP_NAMES property with the new name
456 XAtom::StringVect namesList
;
457 unsigned long numnames
= (unsigned) -1;
458 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
459 XAtom::utf8
, numnames
, namesList
) &&
460 namesList
.size() > id
)
461 namesList
[id
] = new_name
;
463 namesList
.push_back(new_name
);
465 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
466 XAtom::utf8
, namesList
);
471 * Calculate free space available for window placement.
473 typedef std::vector
<Rect
> rectList
;
475 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
478 rectList::const_iterator siter
, end
= spaces
.end();
479 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
480 const Rect
&curr
= *siter
;
482 if(! win
.intersects(curr
)) {
483 result
.push_back(curr
);
487 /* Use an intersection of win and curr to determine the space around
488 * curr that we can use.
490 * NOTE: the spaces calculated can overlap.
495 extra
.setCoords(curr
.left(), curr
.top(),
496 isect
.left() - 1, curr
.bottom());
497 if (extra
.valid()) result
.push_back(extra
);
500 extra
.setCoords(curr
.left(), curr
.top(),
501 curr
.right(), isect
.top() - 1);
502 if (extra
.valid()) result
.push_back(extra
);
505 extra
.setCoords(isect
.right() + 1, curr
.top(),
506 curr
.right(), curr
.bottom());
507 if (extra
.valid()) result
.push_back(extra
);
510 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
511 curr
.right(), curr
.bottom());
512 if (extra
.valid()) result
.push_back(extra
);
518 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
519 if (first
.bottom() == second
.bottom())
520 return first
.right() > second
.right();
521 return first
.bottom() > second
.bottom();
524 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
525 if (first
.y() == second
.y())
526 return first
.right() > second
.right();
527 return first
.y() < second
.y();
530 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
531 if (first
.bottom() == second
.bottom())
532 return first
.x() < second
.x();
533 return first
.bottom() > second
.bottom();
536 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
537 if (first
.y() == second
.y())
538 return first
.x() < second
.x();
539 return first
.y() < second
.y();
542 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
543 if (first
.x() == second
.x())
544 return first
.y() < second
.y();
545 return first
.x() < second
.x();
548 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
549 if (first
.x() == second
.x())
550 return first
.bottom() > second
.bottom();
551 return first
.x() < second
.x();
554 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
555 if (first
.right() == second
.right())
556 return first
.y() < second
.y();
557 return first
.right() > second
.right();
560 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
561 if (first
.right() == second
.right())
562 return first
.bottom() > second
.bottom();
563 return first
.right() > second
.right();
567 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
569 spaces
.push_back(availableArea
); //initially the entire screen is free
572 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
573 end
= windowList
.end();
575 for (; wit
!= end
; ++wit
) {
576 const BlackboxWindow
* const curr
= *wit
;
578 if (curr
->isShaded() && screen
->getPlaceIgnoreShaded()) continue;
579 if (curr
->isMaximizedFull() && screen
->getPlaceIgnoreMaximized()) continue;
581 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
582 curr
->frameRect().width() + screen
->getBorderWidth(),
583 curr
->frameRect().height() + screen
->getBorderWidth());
585 spaces
= calcSpace(tmp
, spaces
);
588 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
589 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
590 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
591 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
593 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
595 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
596 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
598 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
601 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
602 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
603 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
605 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
607 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
608 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
610 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
614 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
615 for(; sit
!= spaces_end
; ++sit
) {
616 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
620 if (sit
== spaces_end
)
623 //set new position based on the empty space found
624 const Rect
& where
= *sit
;
628 // adjust the location() based on left/right and top/bottom placement
629 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
630 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
631 win
.setX(where
.right() - win
.width());
632 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
633 win
.setY(where
.bottom() - win
.height());
635 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
636 win
.setY(win
.y() + where
.height() - win
.height());
637 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
638 win
.setX(win
.x() + where
.width() - win
.width());
644 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
648 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
649 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
650 x
= rx
- win
.width() / 2;
651 y
= ry
- win
.height() / 2;
653 if (x
< availableArea
.x())
654 x
= availableArea
.x();
655 if (y
< availableArea
.y())
656 y
= availableArea
.y();
657 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
658 x
= availableArea
.x() + availableArea
.width() - win
.width();
659 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
660 y
= availableArea
.y() + availableArea
.height() - win
.height();
669 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
670 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
671 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
672 cascade_x
= cascade_y
= 32;
674 if (cascade_x
== 32) {
675 cascade_x
+= availableArea
.x();
676 cascade_y
+= availableArea
.y();
679 win
.setPos(cascade_x
, cascade_y
);
685 void Workspace::placeWindow(BlackboxWindow
*win
) {
686 Rect
availableArea(screen
->availableArea()),
687 new_win(availableArea
.x(), availableArea
.y(),
688 win
->frameRect().width(), win
->frameRect().height());
691 switch (screen
->getPlacementPolicy()) {
692 case BScreen::RowSmartPlacement
:
693 case BScreen::ColSmartPlacement
:
694 placed
= smartPlacement(new_win
, availableArea
);
696 case BScreen::UnderMousePlacement
:
697 case BScreen::ClickMousePlacement
:
698 placed
= underMousePlacement(new_win
, availableArea
);
700 break; // handled below
703 if (placed
== False
) {
704 cascadePlacement(new_win
, availableArea
);
705 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
706 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
709 if (new_win
.right() > availableArea
.right())
710 new_win
.setX(availableArea
.left());
711 if (new_win
.bottom() > availableArea
.bottom())
712 new_win
.setY(availableArea
.top());
713 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());