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"
60 Workspace::Workspace(BScreen
*scrn
, unsigned int i
) {
63 cascade_x
= cascade_y
= 32;
67 clientmenu
= new Clientmenu(this);
69 lastfocus
= (BlackboxWindow
*) 0;
71 setName(screen
->getNameOfWorkspace(id
));
75 void Workspace::addWindow(BlackboxWindow
*w
, bool place
) {
78 if (place
) placeWindow(w
);
81 w
->setWindowNumber(windowList
.size());
83 stackingList
.push_front(w
);
84 windowList
.push_back(w
);
86 clientmenu
->insert(w
->getTitle());
89 screen
->updateNetizenWindowAdd(w
->getClientWindow(), id
);
95 unsigned int Workspace::removeWindow(BlackboxWindow
*w
) {
98 stackingList
.remove(w
);
100 // pass focus to the next appropriate window
101 if ((w
->isFocused() || w
== lastfocus
) &&
102 ! screen
->getBlackbox()->doShutdown()) {
103 BlackboxWindow
*newfocus
= 0;
104 if (w
->isTransient())
105 newfocus
= w
->getTransientFor();
106 if (! newfocus
&& ! stackingList
.empty())
107 newfocus
= stackingList
.front();
109 assert(newfocus
!= w
); // this would be very wrong.
111 if (id
== screen
->getCurrentWorkspaceID()) {
113 if the window is on the visible workspace, then try focus it, and fall
114 back to the default focus target if the window won't focus.
116 if (! newfocus
|| ! newfocus
->setInputFocus())
117 screen
->getBlackbox()->setFocusedWindow(0);
118 } else if (lastfocus
== w
) {
120 If this workspace is not the current one do not assume that
121 w == lastfocus. If a sticky window is removed on a workspace other
122 than where it originated, it will fire the removeWindow on a
123 non-visible workspace.
127 If the window isn't on the visible workspace, don't focus the new one,
128 just mark it to be focused when the workspace comes into view.
130 setLastFocusedWindow(newfocus
);
134 windowList
.remove(w
);
135 clientmenu
->remove(w
->getWindowNumber());
136 clientmenu
->update();
138 screen
->updateNetizenWindowDel(w
->getClientWindow());
140 BlackboxWindowList::iterator it
= windowList
.begin();
141 const BlackboxWindowList::iterator end
= windowList
.end();
143 for (; it
!= end
; ++it
, ++i
)
144 (*it
)->setWindowNumber(i
);
147 cascade_x
= cascade_y
= 32;
153 void Workspace::showAll(void) {
154 std::for_each(stackingList
.begin(), stackingList
.end(),
155 std::mem_fun(&BlackboxWindow::show
));
159 void Workspace::hideAll(void) {
160 // withdraw in reverse order to minimize the number of Expose events
162 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
164 BlackboxWindowList::iterator it
= lst
.begin();
165 const BlackboxWindowList::iterator end
= lst
.end();
166 for (; it
!= end
; ++it
) {
167 BlackboxWindow
*bw
= *it
;
174 void Workspace::removeAll(void) {
175 while (! windowList
.empty())
176 windowList
.front()->iconify();
181 * returns the number of transients for win, plus the number of transients
182 * associated with each transient of win
184 static int countTransients(const BlackboxWindow
* const win
) {
185 int ret
= win
->getTransients().size();
187 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
188 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
189 ret
+= countTransients(*it
);
197 * puts the transients of win into the stack. windows are stacked above
198 * the window before it in the stackvector being iterated, meaning
199 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
202 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
203 StackVector::iterator
&stack
) {
204 if (win
->getTransients().size() == 0) return; // nothing to do
206 // put win's transients in the stack
207 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
208 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
209 *stack
++ = (*it
)->getFrameWindow();
210 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
212 if (! (*it
)->isIconic()) {
213 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
214 wkspc
->stackingList
.remove((*it
));
215 wkspc
->stackingList
.push_front((*it
));
219 // put transients of win's transients in the stack
220 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
221 raiseTransients(*it
, stack
);
226 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
227 StackVector::iterator
&stack
) {
228 if (win
->getTransients().size() == 0) return; // nothing to do
230 // put transients of win's transients in the stack
231 BlackboxWindowList::const_reverse_iterator it
,
232 end
= win
->getTransients().rend();
233 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
234 lowerTransients(*it
, stack
);
237 // put win's transients in the stack
238 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
239 *stack
++ = (*it
)->getFrameWindow();
240 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
242 if (! (*it
)->isIconic()) {
243 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
244 wkspc
->stackingList
.remove((*it
));
245 wkspc
->stackingList
.push_back((*it
));
251 void Workspace::raiseWindow(BlackboxWindow
*w
) {
252 BlackboxWindow
*win
= w
;
254 // walk up the transient_for's to the window that is not a transient
255 while (win
->isTransient()) {
256 if (! win
->getTransientFor()) break;
257 win
= win
->getTransientFor();
260 // get the total window count (win and all transients)
261 unsigned int i
= 1 + countTransients(win
);
263 // stack the window with all transients above
264 StackVector
stack_vector(i
);
265 StackVector::iterator stack
= stack_vector
.begin();
267 *(stack
++) = win
->getFrameWindow();
268 screen
->updateNetizenWindowRaise(win
->getClientWindow());
269 if (! win
->isIconic()) {
270 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
271 wkspc
->stackingList
.remove(win
);
272 wkspc
->stackingList
.push_front(win
);
275 raiseTransients(win
, stack
);
277 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
281 void Workspace::lowerWindow(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 lowerTransients(win
, stack
);
299 *(stack
++) = win
->getFrameWindow();
300 screen
->updateNetizenWindowLower(win
->getClientWindow());
301 if (! win
->isIconic()) {
302 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
303 wkspc
->stackingList
.remove(win
);
304 wkspc
->stackingList
.push_back(win
);
307 XLowerWindow(screen
->getBaseDisplay()->getXDisplay(), stack_vector
.front());
308 XRestackWindows(screen
->getBaseDisplay()->getXDisplay(),
309 &stack_vector
[0], stack_vector
.size());
310 screen
->lowerDesktops();
314 void Workspace::reconfigure(void) {
315 clientmenu
->reconfigure();
316 std::for_each(windowList
.begin(), windowList
.end(),
317 std::mem_fun(&BlackboxWindow::reconfigure
));
321 void Workspace::updateFocusModel(void) {
322 std::for_each(windowList
.begin(), windowList
.end(),
323 std::mem_fun(&BlackboxWindow::updateFocusModel
));
327 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
328 if (index
< windowList
.size()) {
329 BlackboxWindowList::iterator it
= windowList
.begin();
330 for(; index
> 0; --index
, ++it
); /* increment to index */
338 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
339 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
342 assert(it
!= windowList
.end()); // window must be in list
344 if (it
== windowList
.end())
345 return windowList
.front(); // if we walked off the end, wrap around
351 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
352 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
355 assert(it
!= windowList
.end()); // window must be in list
356 if (it
== windowList
.begin())
357 return windowList
.back(); // if we walked of the front, wrap around
363 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
364 return stackingList
.front();
368 void Workspace::sendWindowList(Netizen
&n
) {
369 BlackboxWindowList::iterator it
= windowList
.begin(),
370 end
= windowList
.end();
371 for(; it
!= end
; ++it
)
372 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
376 unsigned int Workspace::getCount(void) const {
377 return windowList
.size();
381 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
382 BlackboxWindowList::const_iterator it
= stackingList
.begin();
383 const BlackboxWindowList::const_iterator end
= stackingList
.end();
384 for (; it
!= end
; ++it
)
385 stack_order
.push_back(*it
);
389 bool Workspace::isCurrent(void) const {
390 return (id
== screen
->getCurrentWorkspaceID());
394 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
395 return (w
== windowList
.back());
398 void Workspace::setCurrent(void) {
399 screen
->changeWorkspaceID(id
);
403 void Workspace::setName(const string
& new_name
) {
404 if (! new_name
.empty()) {
407 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
, "Workspace %d");
408 assert(tmp
.length() < 32);
409 char default_name
[32];
410 sprintf(default_name
, tmp
.c_str(), id
+ 1);
414 clientmenu
->setLabel(name
);
415 clientmenu
->update();
416 screen
->saveWorkspaceNames();
421 * Calculate free space available for window placement.
423 typedef std::vector
<Rect
> rectList
;
425 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
428 rectList::const_iterator siter
, end
= spaces
.end();
429 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
430 const Rect
&curr
= *siter
;
432 if(! win
.intersects(curr
)) {
433 result
.push_back(curr
);
437 /* Use an intersection of win and curr to determine the space around
438 * curr that we can use.
440 * NOTE: the spaces calculated can overlap.
445 extra
.setCoords(curr
.left(), curr
.top(),
446 isect
.left() - 1, curr
.bottom());
447 if (extra
.valid()) result
.push_back(extra
);
450 extra
.setCoords(curr
.left(), curr
.top(),
451 curr
.right(), isect
.top() - 1);
452 if (extra
.valid()) result
.push_back(extra
);
455 extra
.setCoords(isect
.right() + 1, curr
.top(),
456 curr
.right(), curr
.bottom());
457 if (extra
.valid()) result
.push_back(extra
);
460 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
461 curr
.right(), curr
.bottom());
462 if (extra
.valid()) result
.push_back(extra
);
468 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
469 if (first
.bottom() == second
.bottom())
470 return first
.right() > second
.right();
471 return first
.bottom() > second
.bottom();
474 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
475 if (first
.y() == second
.y())
476 return first
.right() > second
.right();
477 return first
.y() < second
.y();
480 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
481 if (first
.bottom() == second
.bottom())
482 return first
.x() < second
.x();
483 return first
.bottom() > second
.bottom();
486 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
487 if (first
.y() == second
.y())
488 return first
.x() < second
.x();
489 return first
.y() < second
.y();
492 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
493 if (first
.x() == second
.x())
494 return first
.y() < second
.y();
495 return first
.x() < second
.x();
498 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
499 if (first
.x() == second
.x())
500 return first
.bottom() > second
.bottom();
501 return first
.x() < second
.x();
504 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
505 if (first
.right() == second
.right())
506 return first
.y() < second
.y();
507 return first
.right() > second
.right();
510 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
511 if (first
.right() == second
.right())
512 return first
.bottom() > second
.bottom();
513 return first
.right() > second
.right();
517 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
519 spaces
.push_back(availableArea
); //initially the entire screen is free
522 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
523 end
= windowList
.end();
525 for (; wit
!= end
; ++wit
) {
526 const BlackboxWindow
* const curr
= *wit
;
528 if (curr
->isShaded()) continue;
530 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
531 curr
->frameRect().width() + screen
->getBorderWidth(),
532 curr
->frameRect().height() + screen
->getBorderWidth());
534 spaces
= calcSpace(tmp
, spaces
);
537 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
538 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
539 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
540 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
542 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
544 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
545 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
547 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
550 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
551 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
552 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
554 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
556 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
557 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
559 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
563 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
564 for(; sit
!= spaces_end
; ++sit
) {
565 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
569 if (sit
== spaces_end
)
572 //set new position based on the empty space found
573 const Rect
& where
= *sit
;
577 // adjust the location() based on left/right and top/bottom placement
578 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
579 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
580 win
.setX(where
.right() - win
.width());
581 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
582 win
.setY(where
.bottom() - win
.height());
584 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
585 win
.setY(win
.y() + where
.height() - win
.height());
586 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
587 win
.setX(win
.x() + where
.width() - win
.width());
593 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
597 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
598 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
599 x
= rx
- win
.width() / 2;
600 y
= ry
- win
.height() / 2;
602 if (x
< availableArea
.x())
603 x
= availableArea
.x();
604 if (y
< availableArea
.y())
605 y
= availableArea
.y();
606 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
607 x
= availableArea
.x() + availableArea
.width() - win
.width();
608 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
609 y
= availableArea
.y() + availableArea
.height() - win
.height();
618 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
619 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
620 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
621 cascade_x
= cascade_y
= 32;
623 if (cascade_x
== 32) {
624 cascade_x
+= availableArea
.x();
625 cascade_y
+= availableArea
.y();
628 win
.setPos(cascade_x
, cascade_y
);
634 void Workspace::placeWindow(BlackboxWindow
*win
) {
635 Rect
availableArea(screen
->availableArea()),
636 new_win(availableArea
.x(), availableArea
.y(),
637 win
->frameRect().width(), win
->frameRect().height());
640 switch (screen
->getPlacementPolicy()) {
641 case BScreen::RowSmartPlacement
:
642 case BScreen::ColSmartPlacement
:
643 placed
= smartPlacement(new_win
, availableArea
);
645 case BScreen::UnderMousePlacement
:
646 placed
= underMousePlacement(new_win
, availableArea
);
648 break; // handled below
651 if (placed
== False
) {
652 cascadePlacement(new_win
, availableArea
);
653 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
654 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
657 if (new_win
.right() > availableArea
.right())
658 new_win
.setX(availableArea
.left());
659 if (new_win
.bottom() > availableArea
.bottom())
660 new_win
.setY(availableArea
.top());
661 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());