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 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
324 if (index
< windowList
.size()) {
325 BlackboxWindowList::iterator it
= windowList
.begin();
326 for(; index
> 0; --index
, ++it
); /* increment to index */
334 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
335 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
338 assert(it
!= windowList
.end()); // window must be in list
340 if (it
== windowList
.end())
341 return windowList
.front(); // if we walked off the end, wrap around
347 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
348 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
351 assert(it
!= windowList
.end()); // window must be in list
352 if (it
== windowList
.begin())
353 return windowList
.back(); // if we walked of the front, wrap around
359 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
360 return stackingList
.front();
364 void Workspace::sendWindowList(Netizen
&n
) {
365 BlackboxWindowList::iterator it
= windowList
.begin(),
366 end
= windowList
.end();
367 for(; it
!= end
; ++it
)
368 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
372 unsigned int Workspace::getCount(void) const {
373 return windowList
.size();
377 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
378 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
379 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
380 for (; it
!= end
; ++it
)
381 stack_order
.push_back(*it
);
385 bool Workspace::isCurrent(void) const {
386 return (id
== screen
->getCurrentWorkspaceID());
390 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
391 return (w
== windowList
.back());
395 void Workspace::setCurrent(void) {
396 screen
->changeWorkspaceID(id
);
400 void Workspace::setName(const string
& new_name
) {
401 if (! new_name
.empty()) {
404 // attempt to get from the _NET_WM_DESKTOP_NAMES property
405 XAtom::StringVect namesList
;
406 unsigned long numnames
= id
+ 1;
407 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
408 XAtom::utf8
, numnames
, namesList
) &&
409 namesList
.size() > id
) {
410 name
= namesList
[id
];
412 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
414 assert(tmp
.length() < 32);
415 char default_name
[32];
416 sprintf(default_name
, tmp
.c_str(), id
+ 1);
421 // reset the property with the new name
422 XAtom::StringVect namesList
;
423 unsigned long numnames
= (unsigned) -1;
424 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
425 XAtom::utf8
, numnames
, namesList
)) {
426 if (namesList
.size() > id
)
427 namesList
[id
] = name
;
429 namesList
.push_back(name
);
431 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
432 XAtom::utf8
, namesList
);
434 clientmenu
->setLabel(name
);
435 clientmenu
->update();
436 screen
->saveWorkspaceNames();
441 * Calculate free space available for window placement.
443 typedef std::vector
<Rect
> rectList
;
445 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
448 rectList::const_iterator siter
, end
= spaces
.end();
449 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
450 const Rect
&curr
= *siter
;
452 if(! win
.intersects(curr
)) {
453 result
.push_back(curr
);
457 /* Use an intersection of win and curr to determine the space around
458 * curr that we can use.
460 * NOTE: the spaces calculated can overlap.
465 extra
.setCoords(curr
.left(), curr
.top(),
466 isect
.left() - 1, curr
.bottom());
467 if (extra
.valid()) result
.push_back(extra
);
470 extra
.setCoords(curr
.left(), curr
.top(),
471 curr
.right(), isect
.top() - 1);
472 if (extra
.valid()) result
.push_back(extra
);
475 extra
.setCoords(isect
.right() + 1, curr
.top(),
476 curr
.right(), curr
.bottom());
477 if (extra
.valid()) result
.push_back(extra
);
480 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
481 curr
.right(), curr
.bottom());
482 if (extra
.valid()) result
.push_back(extra
);
488 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
489 if (first
.bottom() == second
.bottom())
490 return first
.right() > second
.right();
491 return first
.bottom() > second
.bottom();
494 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
495 if (first
.y() == second
.y())
496 return first
.right() > second
.right();
497 return first
.y() < second
.y();
500 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
501 if (first
.bottom() == second
.bottom())
502 return first
.x() < second
.x();
503 return first
.bottom() > second
.bottom();
506 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
507 if (first
.y() == second
.y())
508 return first
.x() < second
.x();
509 return first
.y() < second
.y();
512 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
513 if (first
.x() == second
.x())
514 return first
.y() < second
.y();
515 return first
.x() < second
.x();
518 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
519 if (first
.x() == second
.x())
520 return first
.bottom() > second
.bottom();
521 return first
.x() < second
.x();
524 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
525 if (first
.right() == second
.right())
526 return first
.y() < second
.y();
527 return first
.right() > second
.right();
530 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
531 if (first
.right() == second
.right())
532 return first
.bottom() > second
.bottom();
533 return first
.right() > second
.right();
537 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
539 spaces
.push_back(availableArea
); //initially the entire screen is free
542 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
543 end
= windowList
.end();
545 for (; wit
!= end
; ++wit
) {
546 const BlackboxWindow
* const curr
= *wit
;
548 if (curr
->isShaded()) continue;
550 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
551 curr
->frameRect().width() + screen
->getBorderWidth(),
552 curr
->frameRect().height() + screen
->getBorderWidth());
554 spaces
= calcSpace(tmp
, spaces
);
557 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
558 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
559 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
560 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
562 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
564 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
565 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
567 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
570 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
571 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
572 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
574 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
576 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
577 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
579 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
583 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
584 for(; sit
!= spaces_end
; ++sit
) {
585 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
589 if (sit
== spaces_end
)
592 //set new position based on the empty space found
593 const Rect
& where
= *sit
;
597 // adjust the location() based on left/right and top/bottom placement
598 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
599 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
600 win
.setX(where
.right() - win
.width());
601 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
602 win
.setY(where
.bottom() - win
.height());
604 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
605 win
.setY(win
.y() + where
.height() - win
.height());
606 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
607 win
.setX(win
.x() + where
.width() - win
.width());
613 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
617 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
618 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
619 x
= rx
- win
.width() / 2;
620 y
= ry
- win
.height() / 2;
622 if (x
< availableArea
.x())
623 x
= availableArea
.x();
624 if (y
< availableArea
.y())
625 y
= availableArea
.y();
626 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
627 x
= availableArea
.x() + availableArea
.width() - win
.width();
628 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
629 y
= availableArea
.y() + availableArea
.height() - win
.height();
638 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
639 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
640 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
641 cascade_x
= cascade_y
= 32;
643 if (cascade_x
== 32) {
644 cascade_x
+= availableArea
.x();
645 cascade_y
+= availableArea
.y();
648 win
.setPos(cascade_x
, cascade_y
);
654 void Workspace::placeWindow(BlackboxWindow
*win
) {
655 Rect
availableArea(screen
->availableArea()),
656 new_win(availableArea
.x(), availableArea
.y(),
657 win
->frameRect().width(), win
->frameRect().height());
660 switch (screen
->getPlacementPolicy()) {
661 case BScreen::RowSmartPlacement
:
662 case BScreen::ColSmartPlacement
:
663 placed
= smartPlacement(new_win
, availableArea
);
665 case BScreen::UnderMousePlacement
:
666 placed
= underMousePlacement(new_win
, availableArea
);
668 break; // handled below
671 if (placed
== False
) {
672 cascadePlacement(new_win
, availableArea
);
673 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
674 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
677 if (new_win
.right() > availableArea
.right())
678 new_win
.setX(availableArea
.left());
679 if (new_win
.bottom() > availableArea
.bottom())
680 new_win
.setY(availableArea
.top());
681 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());