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 BlackboxWindow
*newfocus
= 0;
106 if (w
->isTransient())
107 newfocus
= w
->getTransientFor();
108 if (! newfocus
&& ! stackingList
.empty())
109 newfocus
= stackingList
.front();
111 assert(newfocus
!= w
); // this would be very wrong.
113 if (id
== screen
->getCurrentWorkspaceID()) {
115 if the window is on the visible workspace, then try focus it, and fall
116 back to the default focus target if the window won't focus.
118 if (! newfocus
|| ! newfocus
->setInputFocus())
119 screen
->getBlackbox()->setFocusedWindow(0);
120 } else if (lastfocus
== w
) {
122 If this workspace is not the current one do not assume that
123 w == lastfocus. If a sticky window is removed on a workspace other
124 than where it originated, it will fire the removeWindow on a
125 non-visible workspace.
129 If the window isn't on the visible workspace, don't focus the new one,
130 just mark it to be focused when the workspace comes into view.
132 setLastFocusedWindow(newfocus
);
136 windowList
.remove(w
);
137 clientmenu
->remove(w
->getWindowNumber());
138 clientmenu
->update();
140 screen
->updateNetizenWindowDel(w
->getClientWindow());
142 BlackboxWindowList::iterator it
= windowList
.begin();
143 const BlackboxWindowList::iterator end
= windowList
.end();
145 for (; it
!= end
; ++it
, ++i
)
146 (*it
)->setWindowNumber(i
);
149 cascade_x
= cascade_y
= 32;
155 void Workspace::showAll(void) {
156 std::for_each(stackingList
.begin(), stackingList
.end(),
157 std::mem_fun(&BlackboxWindow::show
));
161 void Workspace::hideAll(void) {
162 // withdraw in reverse order to minimize the number of Expose events
164 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
166 BlackboxWindowList::iterator it
= lst
.begin();
167 const BlackboxWindowList::iterator end
= lst
.end();
168 for (; it
!= end
; ++it
) {
169 BlackboxWindow
*bw
= *it
;
176 void Workspace::removeAll(void) {
177 while (! windowList
.empty())
178 windowList
.front()->iconify();
183 * returns the number of transients for win, plus the number of transients
184 * associated with each transient of win
186 static int countTransients(const BlackboxWindow
* const win
) {
187 int ret
= win
->getTransients().size();
189 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
190 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
191 ret
+= countTransients(*it
);
199 * puts the transients of win into the stack. windows are stacked above
200 * the window before it in the stackvector being iterated, meaning
201 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
204 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
205 StackVector::iterator
&stack
) {
206 if (win
->getTransients().size() == 0) return; // nothing to do
208 // put win's transients in the stack
209 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
210 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
211 *stack
++ = (*it
)->getFrameWindow();
212 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
214 if (! (*it
)->isIconic()) {
215 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
216 wkspc
->stackingList
.remove((*it
));
217 wkspc
->stackingList
.push_front((*it
));
221 // put transients of win's transients in the stack
222 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
223 raiseTransients(*it
, stack
);
228 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
229 StackVector::iterator
&stack
) {
230 if (win
->getTransients().size() == 0) return; // nothing to do
232 // put transients of win's transients in the stack
233 BlackboxWindowList::const_reverse_iterator it
,
234 end
= win
->getTransients().rend();
235 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
236 lowerTransients(*it
, stack
);
239 // put win's transients in the stack
240 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
241 *stack
++ = (*it
)->getFrameWindow();
242 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
244 if (! (*it
)->isIconic()) {
245 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
246 wkspc
->stackingList
.remove((*it
));
247 wkspc
->stackingList
.push_back((*it
));
253 void Workspace::raiseWindow(BlackboxWindow
*w
) {
254 BlackboxWindow
*win
= w
;
256 // walk up the transient_for's to the window that is not a transient
257 while (win
->isTransient()) {
258 if (! win
->getTransientFor()) break;
259 win
= win
->getTransientFor();
262 // get the total window count (win and all transients)
263 unsigned int i
= 1 + countTransients(win
);
265 // stack the window with all transients above
266 StackVector
stack_vector(i
);
267 StackVector::iterator stack
= stack_vector
.begin();
269 *(stack
++) = win
->getFrameWindow();
270 screen
->updateNetizenWindowRaise(win
->getClientWindow());
271 if (! win
->isIconic()) {
272 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
273 wkspc
->stackingList
.remove(win
);
274 wkspc
->stackingList
.push_front(win
);
277 raiseTransients(win
, stack
);
279 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
283 void Workspace::lowerWindow(BlackboxWindow
*w
) {
284 BlackboxWindow
*win
= w
;
286 // walk up the transient_for's to the window that is not a transient
287 while (win
->isTransient()) {
288 if (! win
->getTransientFor()) break;
289 win
= win
->getTransientFor();
292 // get the total window count (win and all transients)
293 unsigned int i
= 1 + countTransients(win
);
295 // stack the window with all transients above
296 StackVector
stack_vector(i
);
297 StackVector::iterator stack
= stack_vector
.begin();
299 lowerTransients(win
, stack
);
301 *(stack
++) = win
->getFrameWindow();
302 screen
->updateNetizenWindowLower(win
->getClientWindow());
303 if (! win
->isIconic()) {
304 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
305 wkspc
->stackingList
.remove(win
);
306 wkspc
->stackingList
.push_back(win
);
309 XLowerWindow(screen
->getBaseDisplay()->getXDisplay(), stack_vector
.front());
310 XRestackWindows(screen
->getBaseDisplay()->getXDisplay(),
311 &stack_vector
[0], stack_vector
.size());
312 screen
->lowerDesktops();
316 void Workspace::reconfigure(void) {
317 clientmenu
->reconfigure();
318 std::for_each(windowList
.begin(), windowList
.end(),
319 std::mem_fun(&BlackboxWindow::reconfigure
));
323 void Workspace::updateFocusModel(void) {
324 std::for_each(windowList
.begin(), windowList
.end(),
325 std::mem_fun(&BlackboxWindow::updateFocusModel
));
329 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
330 if (index
< windowList
.size()) {
331 BlackboxWindowList::iterator it
= windowList
.begin();
332 for(; index
> 0; --index
, ++it
); /* increment to index */
340 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
341 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
344 assert(it
!= windowList
.end()); // window must be in list
346 if (it
== windowList
.end())
347 return windowList
.front(); // if we walked off the end, wrap around
353 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
354 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
357 assert(it
!= windowList
.end()); // window must be in list
358 if (it
== windowList
.begin())
359 return windowList
.back(); // if we walked of the front, wrap around
365 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
366 return stackingList
.front();
370 void Workspace::sendWindowList(Netizen
&n
) {
371 BlackboxWindowList::iterator it
= windowList
.begin(),
372 end
= windowList
.end();
373 for(; it
!= end
; ++it
)
374 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
378 unsigned int Workspace::getCount(void) const {
379 return windowList
.size();
383 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
384 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
385 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
386 for (; it
!= end
; ++it
)
387 stack_order
.push_back(*it
);
391 bool Workspace::isCurrent(void) const {
392 return (id
== screen
->getCurrentWorkspaceID());
396 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
397 return (w
== windowList
.back());
401 void Workspace::setCurrent(void) {
402 screen
->changeWorkspaceID(id
);
406 void Workspace::setName(const string
& new_name
) {
407 if (! new_name
.empty()) {
410 // attempt to get from the _NET_WM_DESKTOP_NAMES property
411 XAtom::StringVect namesList
;
412 unsigned long numnames
= id
+ 1;
413 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
414 XAtom::utf8
, numnames
, namesList
) &&
415 namesList
.size() > id
) {
416 name
= namesList
[id
];
418 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
420 assert(tmp
.length() < 32);
421 char default_name
[32];
422 sprintf(default_name
, tmp
.c_str(), id
+ 1);
427 // reset the property with the new name
428 XAtom::StringVect namesList
;
429 unsigned long numnames
= (unsigned) -1;
430 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
431 XAtom::utf8
, numnames
, namesList
)) {
432 if (namesList
.size() > id
)
433 namesList
[id
] = name
;
435 namesList
.push_back(name
);
437 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
438 XAtom::utf8
, namesList
);
440 clientmenu
->setLabel(name
);
441 clientmenu
->update();
442 screen
->saveWorkspaceNames();
447 * Calculate free space available for window placement.
449 typedef std::vector
<Rect
> rectList
;
451 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
454 rectList::const_iterator siter
, end
= spaces
.end();
455 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
456 const Rect
&curr
= *siter
;
458 if(! win
.intersects(curr
)) {
459 result
.push_back(curr
);
463 /* Use an intersection of win and curr to determine the space around
464 * curr that we can use.
466 * NOTE: the spaces calculated can overlap.
471 extra
.setCoords(curr
.left(), curr
.top(),
472 isect
.left() - 1, curr
.bottom());
473 if (extra
.valid()) result
.push_back(extra
);
476 extra
.setCoords(curr
.left(), curr
.top(),
477 curr
.right(), isect
.top() - 1);
478 if (extra
.valid()) result
.push_back(extra
);
481 extra
.setCoords(isect
.right() + 1, curr
.top(),
482 curr
.right(), curr
.bottom());
483 if (extra
.valid()) result
.push_back(extra
);
486 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
487 curr
.right(), curr
.bottom());
488 if (extra
.valid()) result
.push_back(extra
);
494 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
495 if (first
.bottom() == second
.bottom())
496 return first
.right() > second
.right();
497 return first
.bottom() > second
.bottom();
500 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
501 if (first
.y() == second
.y())
502 return first
.right() > second
.right();
503 return first
.y() < second
.y();
506 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
507 if (first
.bottom() == second
.bottom())
508 return first
.x() < second
.x();
509 return first
.bottom() > second
.bottom();
512 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
513 if (first
.y() == second
.y())
514 return first
.x() < second
.x();
515 return first
.y() < second
.y();
518 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
519 if (first
.x() == second
.x())
520 return first
.y() < second
.y();
521 return first
.x() < second
.x();
524 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
525 if (first
.x() == second
.x())
526 return first
.bottom() > second
.bottom();
527 return first
.x() < second
.x();
530 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
531 if (first
.right() == second
.right())
532 return first
.y() < second
.y();
533 return first
.right() > second
.right();
536 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
537 if (first
.right() == second
.right())
538 return first
.bottom() > second
.bottom();
539 return first
.right() > second
.right();
543 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
545 spaces
.push_back(availableArea
); //initially the entire screen is free
548 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
549 end
= windowList
.end();
551 for (; wit
!= end
; ++wit
) {
552 const BlackboxWindow
* const curr
= *wit
;
554 if (curr
->isShaded()) continue;
556 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
557 curr
->frameRect().width() + screen
->getBorderWidth(),
558 curr
->frameRect().height() + screen
->getBorderWidth());
560 spaces
= calcSpace(tmp
, spaces
);
563 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
564 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
565 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
566 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
568 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
570 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
571 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
573 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
576 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
577 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
578 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
580 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
582 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
583 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
585 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
589 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
590 for(; sit
!= spaces_end
; ++sit
) {
591 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
595 if (sit
== spaces_end
)
598 //set new position based on the empty space found
599 const Rect
& where
= *sit
;
603 // adjust the location() based on left/right and top/bottom placement
604 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
605 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
606 win
.setX(where
.right() - win
.width());
607 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
608 win
.setY(where
.bottom() - win
.height());
610 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
611 win
.setY(win
.y() + where
.height() - win
.height());
612 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
613 win
.setX(win
.x() + where
.width() - win
.width());
619 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
623 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
624 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
625 x
= rx
- win
.width() / 2;
626 y
= ry
- win
.height() / 2;
628 if (x
< availableArea
.x())
629 x
= availableArea
.x();
630 if (y
< availableArea
.y())
631 y
= availableArea
.y();
632 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
633 x
= availableArea
.x() + availableArea
.width() - win
.width();
634 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
635 y
= availableArea
.y() + availableArea
.height() - win
.height();
644 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
645 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
646 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
647 cascade_x
= cascade_y
= 32;
649 if (cascade_x
== 32) {
650 cascade_x
+= availableArea
.x();
651 cascade_y
+= availableArea
.y();
654 win
.setPos(cascade_x
, cascade_y
);
660 void Workspace::placeWindow(BlackboxWindow
*win
) {
661 Rect
availableArea(screen
->availableArea()),
662 new_win(availableArea
.x(), availableArea
.y(),
663 win
->frameRect().width(), win
->frameRect().height());
666 switch (screen
->getPlacementPolicy()) {
667 case BScreen::RowSmartPlacement
:
668 case BScreen::ColSmartPlacement
:
669 placed
= smartPlacement(new_win
, availableArea
);
671 case BScreen::UnderMousePlacement
:
672 placed
= underMousePlacement(new_win
, availableArea
);
674 break; // handled below
677 if (placed
== False
) {
678 cascadePlacement(new_win
, availableArea
);
679 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
680 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
683 if (new_win
.right() > availableArea
.right())
684 new_win
.setX(availableArea
.left());
685 if (new_win
.bottom() > availableArea
.bottom())
686 new_win
.setY(availableArea
.top());
687 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());