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()) {
105 if (id
== screen
->getCurrentWorkspaceID()) {
106 // The window is on the visible workspace
109 // The window is not on the visible workspace.
110 if (lastfocus
== w
) {
111 // The window was the last-focus target, so we need to replace it.
112 setLastFocusedWindow(stackingList
.front());
114 // if the window focused on the current workspace, then reapply that
115 // workspace's focus too
117 screen
->getCurrentWorkspace()->focusFallback(w
);
121 windowList
.remove(w
);
122 clientmenu
->remove(w
->getWindowNumber());
123 clientmenu
->update();
125 screen
->updateNetizenWindowDel(w
->getClientWindow());
127 BlackboxWindowList::iterator it
= windowList
.begin();
128 const BlackboxWindowList::iterator end
= windowList
.end();
130 for (; it
!= end
; ++it
, ++i
)
131 (*it
)->setWindowNumber(i
);
134 cascade_x
= cascade_y
= 32;
140 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
141 BlackboxWindow
*newfocus
= 0;
143 // if it's a transient, then try to focus its parent
144 if (old_window
&& old_window
->isTransient()) {
145 newfocus
= old_window
->getTransientFor();
148 newfocus
->isIconic() || // do not focus icons
149 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
150 ! newfocus
->setInputFocus())
155 BlackboxWindowList::iterator it
= stackingList
.begin(),
156 end
= stackingList
.end();
157 for (; it
!= end
; ++it
) {
158 BlackboxWindow
*tmp
= *it
;
159 if (tmp
&& tmp
->setInputFocus()) {
160 // we found our new focus target
167 screen
->getBlackbox()->setFocusedWindow(newfocus
);
171 void Workspace::showAll(void) {
172 std::for_each(stackingList
.begin(), stackingList
.end(),
173 std::mem_fun(&BlackboxWindow::show
));
177 void Workspace::hideAll(void) {
178 // withdraw in reverse order to minimize the number of Expose events
180 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
182 BlackboxWindowList::iterator it
= lst
.begin();
183 const BlackboxWindowList::iterator end
= lst
.end();
184 for (; it
!= end
; ++it
) {
185 BlackboxWindow
*bw
= *it
;
192 void Workspace::removeAll(void) {
193 while (! windowList
.empty())
194 windowList
.front()->iconify();
199 * returns the number of transients for win, plus the number of transients
200 * associated with each transient of win
202 static int countTransients(const BlackboxWindow
* const win
) {
203 int ret
= win
->getTransients().size();
205 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
206 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
207 ret
+= countTransients(*it
);
215 * puts the transients of win into the stack. windows are stacked above
216 * the window before it in the stackvector being iterated, meaning
217 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
220 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
221 StackVector::iterator
&stack
) {
222 if (win
->getTransients().size() == 0) return; // nothing to do
224 // put win's transients in the stack
225 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
226 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
227 *stack
++ = (*it
)->getFrameWindow();
228 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
230 if (! (*it
)->isIconic()) {
231 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
232 wkspc
->stackingList
.remove((*it
));
233 wkspc
->stackingList
.push_front((*it
));
237 // put transients of win's transients in the stack
238 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
239 raiseTransients(*it
, stack
);
244 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
245 StackVector::iterator
&stack
) {
246 if (win
->getTransients().size() == 0) return; // nothing to do
248 // put transients of win's transients in the stack
249 BlackboxWindowList::const_reverse_iterator it
,
250 end
= win
->getTransients().rend();
251 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
252 lowerTransients(*it
, stack
);
255 // put win's transients in the stack
256 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
257 *stack
++ = (*it
)->getFrameWindow();
258 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
260 if (! (*it
)->isIconic()) {
261 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
262 wkspc
->stackingList
.remove((*it
));
263 wkspc
->stackingList
.push_back((*it
));
269 void Workspace::raiseWindow(BlackboxWindow
*w
) {
270 BlackboxWindow
*win
= w
;
272 // walk up the transient_for's to the window that is not a transient
273 while (win
->isTransient()) {
274 if (! win
->getTransientFor()) break;
275 win
= win
->getTransientFor();
278 // get the total window count (win and all transients)
279 unsigned int i
= 1 + countTransients(win
);
281 // stack the window with all transients above
282 StackVector
stack_vector(i
);
283 StackVector::iterator stack
= stack_vector
.begin();
285 *(stack
++) = win
->getFrameWindow();
286 screen
->updateNetizenWindowRaise(win
->getClientWindow());
287 if (! win
->isIconic()) {
288 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
289 wkspc
->stackingList
.remove(win
);
290 wkspc
->stackingList
.push_front(win
);
293 raiseTransients(win
, stack
);
295 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
299 void Workspace::lowerWindow(BlackboxWindow
*w
) {
300 BlackboxWindow
*win
= w
;
302 // walk up the transient_for's to the window that is not a transient
303 while (win
->isTransient()) {
304 if (! win
->getTransientFor()) break;
305 win
= win
->getTransientFor();
308 // get the total window count (win and all transients)
309 unsigned int i
= 1 + countTransients(win
);
311 // stack the window with all transients above
312 StackVector
stack_vector(i
);
313 StackVector::iterator stack
= stack_vector
.begin();
315 lowerTransients(win
, stack
);
317 *(stack
++) = win
->getFrameWindow();
318 screen
->updateNetizenWindowLower(win
->getClientWindow());
319 if (! win
->isIconic()) {
320 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
321 wkspc
->stackingList
.remove(win
);
322 wkspc
->stackingList
.push_back(win
);
325 XLowerWindow(screen
->getBaseDisplay()->getXDisplay(), stack_vector
.front());
326 XRestackWindows(screen
->getBaseDisplay()->getXDisplay(),
327 &stack_vector
[0], stack_vector
.size());
328 screen
->lowerDesktops();
332 void Workspace::reconfigure(void) {
333 clientmenu
->reconfigure();
334 std::for_each(windowList
.begin(), windowList
.end(),
335 std::mem_fun(&BlackboxWindow::reconfigure
));
339 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
340 if (index
< windowList
.size()) {
341 BlackboxWindowList::iterator it
= windowList
.begin();
342 for(; index
> 0; --index
, ++it
); /* increment to index */
350 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
351 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
354 assert(it
!= windowList
.end()); // window must be in list
356 if (it
== windowList
.end())
357 return windowList
.front(); // if we walked off the end, wrap around
363 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
364 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
367 assert(it
!= windowList
.end()); // window must be in list
368 if (it
== windowList
.begin())
369 return windowList
.back(); // if we walked of the front, wrap around
375 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
376 return stackingList
.front();
380 void Workspace::sendWindowList(Netizen
&n
) {
381 BlackboxWindowList::iterator it
= windowList
.begin(),
382 end
= windowList
.end();
383 for(; it
!= end
; ++it
)
384 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
388 unsigned int Workspace::getCount(void) const {
389 return windowList
.size();
393 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
394 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
395 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
396 for (; it
!= end
; ++it
)
397 stack_order
.push_back(*it
);
401 bool Workspace::isCurrent(void) const {
402 return (id
== screen
->getCurrentWorkspaceID());
406 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
407 return (w
== windowList
.back());
411 void Workspace::setCurrent(void) {
412 screen
->changeWorkspaceID(id
);
416 void Workspace::setName(const string
& new_name
) {
417 if (! new_name
.empty()) {
420 // attempt to get from the _NET_WM_DESKTOP_NAMES property
421 XAtom::StringVect namesList
;
422 unsigned long numnames
= id
+ 1;
423 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
424 XAtom::utf8
, numnames
, namesList
) &&
425 namesList
.size() > id
) {
426 name
= namesList
[id
];
428 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
430 assert(tmp
.length() < 32);
431 char default_name
[32];
432 sprintf(default_name
, tmp
.c_str(), id
+ 1);
437 // reset the property with the new name
438 XAtom::StringVect namesList
;
439 unsigned long numnames
= (unsigned) -1;
440 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
441 XAtom::utf8
, numnames
, namesList
)) {
442 if (namesList
.size() > id
)
443 namesList
[id
] = name
;
445 namesList
.push_back(name
);
447 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
448 XAtom::utf8
, namesList
);
450 clientmenu
->setLabel(name
);
451 clientmenu
->update();
452 screen
->saveWorkspaceNames();
457 * Calculate free space available for window placement.
459 typedef std::vector
<Rect
> rectList
;
461 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
464 rectList::const_iterator siter
, end
= spaces
.end();
465 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
466 const Rect
&curr
= *siter
;
468 if(! win
.intersects(curr
)) {
469 result
.push_back(curr
);
473 /* Use an intersection of win and curr to determine the space around
474 * curr that we can use.
476 * NOTE: the spaces calculated can overlap.
481 extra
.setCoords(curr
.left(), curr
.top(),
482 isect
.left() - 1, curr
.bottom());
483 if (extra
.valid()) result
.push_back(extra
);
486 extra
.setCoords(curr
.left(), curr
.top(),
487 curr
.right(), isect
.top() - 1);
488 if (extra
.valid()) result
.push_back(extra
);
491 extra
.setCoords(isect
.right() + 1, curr
.top(),
492 curr
.right(), curr
.bottom());
493 if (extra
.valid()) result
.push_back(extra
);
496 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
497 curr
.right(), curr
.bottom());
498 if (extra
.valid()) result
.push_back(extra
);
504 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
505 if (first
.bottom() == second
.bottom())
506 return first
.right() > second
.right();
507 return first
.bottom() > second
.bottom();
510 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
511 if (first
.y() == second
.y())
512 return first
.right() > second
.right();
513 return first
.y() < second
.y();
516 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
517 if (first
.bottom() == second
.bottom())
518 return first
.x() < second
.x();
519 return first
.bottom() > second
.bottom();
522 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
523 if (first
.y() == second
.y())
524 return first
.x() < second
.x();
525 return first
.y() < second
.y();
528 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
529 if (first
.x() == second
.x())
530 return first
.y() < second
.y();
531 return first
.x() < second
.x();
534 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
535 if (first
.x() == second
.x())
536 return first
.bottom() > second
.bottom();
537 return first
.x() < second
.x();
540 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
541 if (first
.right() == second
.right())
542 return first
.y() < second
.y();
543 return first
.right() > second
.right();
546 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
547 if (first
.right() == second
.right())
548 return first
.bottom() > second
.bottom();
549 return first
.right() > second
.right();
553 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
555 spaces
.push_back(availableArea
); //initially the entire screen is free
558 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
559 end
= windowList
.end();
561 for (; wit
!= end
; ++wit
) {
562 const BlackboxWindow
* const curr
= *wit
;
564 if (curr
->isShaded()) continue;
566 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
567 curr
->frameRect().width() + screen
->getBorderWidth(),
568 curr
->frameRect().height() + screen
->getBorderWidth());
570 spaces
= calcSpace(tmp
, spaces
);
573 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
574 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
575 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
576 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
578 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
580 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
581 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
583 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
586 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
587 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
588 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
590 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
592 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
593 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
595 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
599 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
600 for(; sit
!= spaces_end
; ++sit
) {
601 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
605 if (sit
== spaces_end
)
608 //set new position based on the empty space found
609 const Rect
& where
= *sit
;
613 // adjust the location() based on left/right and top/bottom placement
614 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
615 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
616 win
.setX(where
.right() - win
.width());
617 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
618 win
.setY(where
.bottom() - win
.height());
620 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
621 win
.setY(win
.y() + where
.height() - win
.height());
622 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
623 win
.setX(win
.x() + where
.width() - win
.width());
629 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
633 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
634 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
635 x
= rx
- win
.width() / 2;
636 y
= ry
- win
.height() / 2;
638 if (x
< availableArea
.x())
639 x
= availableArea
.x();
640 if (y
< availableArea
.y())
641 y
= availableArea
.y();
642 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
643 x
= availableArea
.x() + availableArea
.width() - win
.width();
644 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
645 y
= availableArea
.y() + availableArea
.height() - win
.height();
654 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
655 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
656 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
657 cascade_x
= cascade_y
= 32;
659 if (cascade_x
== 32) {
660 cascade_x
+= availableArea
.x();
661 cascade_y
+= availableArea
.y();
664 win
.setPos(cascade_x
, cascade_y
);
670 void Workspace::placeWindow(BlackboxWindow
*win
) {
671 Rect
availableArea(screen
->availableArea()),
672 new_win(availableArea
.x(), availableArea
.y(),
673 win
->frameRect().width(), win
->frameRect().height());
676 switch (screen
->getPlacementPolicy()) {
677 case BScreen::RowSmartPlacement
:
678 case BScreen::ColSmartPlacement
:
679 placed
= smartPlacement(new_win
, availableArea
);
681 case BScreen::UnderMousePlacement
:
682 placed
= underMousePlacement(new_win
, availableArea
);
684 break; // handled below
687 if (placed
== False
) {
688 cascadePlacement(new_win
, availableArea
);
689 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
690 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
693 if (new_win
.right() > availableArea
.right())
694 new_win
.setX(availableArea
.left());
695 if (new_win
.bottom() > availableArea
.bottom())
696 new_win
.setY(availableArea
.top());
697 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());