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;
74 fprintf(stderr
, "WORKSPACE NAME: %s\n", name
.c_str());
78 void Workspace::addWindow(BlackboxWindow
*w
, bool place
) {
81 if (place
) placeWindow(w
);
84 w
->setWindowNumber(windowList
.size());
86 stackingList
.push_front(w
);
87 windowList
.push_back(w
);
89 clientmenu
->insert(w
->getTitle());
92 screen
->updateNetizenWindowAdd(w
->getClientWindow(), id
);
98 unsigned int Workspace::removeWindow(BlackboxWindow
*w
) {
101 stackingList
.remove(w
);
103 // pass focus to the next appropriate window
104 if ((w
->isFocused() || w
== lastfocus
) &&
105 ! screen
->getBlackbox()->doShutdown()) {
106 if (id
== screen
->getCurrentWorkspaceID()) {
107 // The window is on the visible workspace
110 // The window is not on the visible workspace.
111 if (lastfocus
== w
) {
112 // The window was the last-focus target, so we need to replace it.
113 setLastFocusedWindow(stackingList
.front());
115 // if the window focused on the current workspace, then reapply that
116 // workspace's focus too
118 screen
->getCurrentWorkspace()->focusFallback(w
);
122 windowList
.remove(w
);
123 clientmenu
->remove(w
->getWindowNumber());
124 clientmenu
->update();
126 screen
->updateNetizenWindowDel(w
->getClientWindow());
128 BlackboxWindowList::iterator it
= windowList
.begin();
129 const BlackboxWindowList::iterator end
= windowList
.end();
131 for (; it
!= end
; ++it
, ++i
)
132 (*it
)->setWindowNumber(i
);
135 cascade_x
= cascade_y
= 32;
141 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
142 BlackboxWindow
*newfocus
= 0;
144 // if it's a transient, then try to focus its parent
145 if (old_window
&& old_window
->isTransient()) {
146 newfocus
= old_window
->getTransientFor();
149 newfocus
->isIconic() || // do not focus icons
150 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
151 ! newfocus
->setInputFocus())
156 BlackboxWindowList::iterator it
= stackingList
.begin(),
157 end
= stackingList
.end();
158 for (; it
!= end
; ++it
) {
159 BlackboxWindow
*tmp
= *it
;
160 if (tmp
&& tmp
->setInputFocus()) {
161 // we found our new focus target
168 screen
->getBlackbox()->setFocusedWindow(newfocus
);
172 void Workspace::showAll(void) {
173 std::for_each(stackingList
.begin(), stackingList
.end(),
174 std::mem_fun(&BlackboxWindow::show
));
178 void Workspace::hideAll(void) {
179 // withdraw in reverse order to minimize the number of Expose events
181 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
183 BlackboxWindowList::iterator it
= lst
.begin();
184 const BlackboxWindowList::iterator end
= lst
.end();
185 for (; it
!= end
; ++it
) {
186 BlackboxWindow
*bw
= *it
;
193 void Workspace::removeAll(void) {
194 while (! windowList
.empty())
195 windowList
.front()->iconify();
200 * returns the number of transients for win, plus the number of transients
201 * associated with each transient of win
203 static int countTransients(const BlackboxWindow
* const win
) {
204 int ret
= win
->getTransients().size();
206 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
207 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
208 ret
+= countTransients(*it
);
216 * puts the transients of win into the stack. windows are stacked above
217 * the window before it in the stackvector being iterated, meaning
218 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
221 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
222 StackVector::iterator
&stack
) {
223 if (win
->getTransients().size() == 0) return; // nothing to do
225 // put win's transients in the stack
226 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
227 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
228 *stack
++ = (*it
)->getFrameWindow();
229 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
231 if (! (*it
)->isIconic()) {
232 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
233 wkspc
->stackingList
.remove((*it
));
234 wkspc
->stackingList
.push_front((*it
));
238 // put transients of win's transients in the stack
239 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
240 raiseTransients(*it
, stack
);
245 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
246 StackVector::iterator
&stack
) {
247 if (win
->getTransients().size() == 0) return; // nothing to do
249 // put transients of win's transients in the stack
250 BlackboxWindowList::const_reverse_iterator it
,
251 end
= win
->getTransients().rend();
252 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
253 lowerTransients(*it
, stack
);
256 // put win's transients in the stack
257 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
258 *stack
++ = (*it
)->getFrameWindow();
259 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
261 if (! (*it
)->isIconic()) {
262 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
263 wkspc
->stackingList
.remove((*it
));
264 wkspc
->stackingList
.push_back((*it
));
270 void Workspace::raiseWindow(BlackboxWindow
*w
) {
271 BlackboxWindow
*win
= w
;
273 // walk up the transient_for's to the window that is not a transient
274 while (win
->isTransient()) {
275 if (! win
->getTransientFor()) break;
276 win
= win
->getTransientFor();
279 // get the total window count (win and all transients)
280 unsigned int i
= 1 + countTransients(win
);
282 // stack the window with all transients above
283 StackVector
stack_vector(i
);
284 StackVector::iterator stack
= stack_vector
.begin();
286 *(stack
++) = win
->getFrameWindow();
287 screen
->updateNetizenWindowRaise(win
->getClientWindow());
288 if (! win
->isIconic()) {
289 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
290 wkspc
->stackingList
.remove(win
);
291 wkspc
->stackingList
.push_front(win
);
294 raiseTransients(win
, stack
);
296 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
300 void Workspace::lowerWindow(BlackboxWindow
*w
) {
301 BlackboxWindow
*win
= w
;
303 // walk up the transient_for's to the window that is not a transient
304 while (win
->isTransient()) {
305 if (! win
->getTransientFor()) break;
306 win
= win
->getTransientFor();
309 // get the total window count (win and all transients)
310 unsigned int i
= 1 + countTransients(win
);
312 // stack the window with all transients above
313 StackVector
stack_vector(i
);
314 StackVector::iterator stack
= stack_vector
.begin();
316 lowerTransients(win
, stack
);
318 *(stack
++) = win
->getFrameWindow();
319 screen
->updateNetizenWindowLower(win
->getClientWindow());
320 if (! win
->isIconic()) {
321 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
322 wkspc
->stackingList
.remove(win
);
323 wkspc
->stackingList
.push_back(win
);
326 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
330 void Workspace::reconfigure(void) {
331 clientmenu
->reconfigure();
332 std::for_each(windowList
.begin(), windowList
.end(),
333 std::mem_fun(&BlackboxWindow::reconfigure
));
337 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
338 if (index
< windowList
.size()) {
339 BlackboxWindowList::iterator it
= windowList
.begin();
340 for(; index
> 0; --index
, ++it
); /* increment to index */
348 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
349 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
352 assert(it
!= windowList
.end()); // window must be in list
354 if (it
== windowList
.end())
355 return windowList
.front(); // if we walked off the end, wrap around
361 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
362 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
365 assert(it
!= windowList
.end()); // window must be in list
366 if (it
== windowList
.begin())
367 return windowList
.back(); // if we walked of the front, wrap around
373 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
374 return stackingList
.front();
378 void Workspace::sendWindowList(Netizen
&n
) {
379 BlackboxWindowList::iterator it
= windowList
.begin(),
380 end
= windowList
.end();
381 for(; it
!= end
; ++it
)
382 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
386 unsigned int Workspace::getCount(void) const {
387 return windowList
.size();
391 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
392 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
393 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
394 for (; it
!= end
; ++it
)
395 stack_order
.push_back(*it
);
399 bool Workspace::isCurrent(void) const {
400 return (id
== screen
->getCurrentWorkspaceID());
404 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
405 return (w
== windowList
.back());
409 void Workspace::setCurrent(void) {
410 screen
->changeWorkspaceID(id
);
414 void Workspace::setName(const string
& new_name
) {
415 if (! new_name
.empty()) {
418 // attempt to get from the _NET_WM_DESKTOP_NAMES property
419 XAtom::StringVect namesList
;
420 unsigned long numnames
= id
+ 1;
421 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
422 XAtom::utf8
, numnames
, namesList
) &&
423 namesList
.size() > id
) {
424 name
= namesList
[id
];
426 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
428 assert(tmp
.length() < 32);
429 char default_name
[32];
430 sprintf(default_name
, tmp
.c_str(), id
+ 1);
435 // reset the property with the new name
436 XAtom::StringVect namesList
;
437 unsigned long numnames
= (unsigned) -1;
438 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
439 XAtom::utf8
, numnames
, namesList
) &&
440 namesList
.size() > id
)
441 namesList
[id
] = name
;
443 namesList
.push_back(name
);
445 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
446 XAtom::utf8
, namesList
);
448 clientmenu
->setLabel(name
);
449 clientmenu
->update();
450 screen
->saveWorkspaceNames();
455 * Calculate free space available for window placement.
457 typedef std::vector
<Rect
> rectList
;
459 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
462 rectList::const_iterator siter
, end
= spaces
.end();
463 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
464 const Rect
&curr
= *siter
;
466 if(! win
.intersects(curr
)) {
467 result
.push_back(curr
);
471 /* Use an intersection of win and curr to determine the space around
472 * curr that we can use.
474 * NOTE: the spaces calculated can overlap.
479 extra
.setCoords(curr
.left(), curr
.top(),
480 isect
.left() - 1, curr
.bottom());
481 if (extra
.valid()) result
.push_back(extra
);
484 extra
.setCoords(curr
.left(), curr
.top(),
485 curr
.right(), isect
.top() - 1);
486 if (extra
.valid()) result
.push_back(extra
);
489 extra
.setCoords(isect
.right() + 1, curr
.top(),
490 curr
.right(), curr
.bottom());
491 if (extra
.valid()) result
.push_back(extra
);
494 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
495 curr
.right(), curr
.bottom());
496 if (extra
.valid()) result
.push_back(extra
);
502 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
503 if (first
.bottom() == second
.bottom())
504 return first
.right() > second
.right();
505 return first
.bottom() > second
.bottom();
508 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
509 if (first
.y() == second
.y())
510 return first
.right() > second
.right();
511 return first
.y() < second
.y();
514 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
515 if (first
.bottom() == second
.bottom())
516 return first
.x() < second
.x();
517 return first
.bottom() > second
.bottom();
520 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
521 if (first
.y() == second
.y())
522 return first
.x() < second
.x();
523 return first
.y() < second
.y();
526 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
527 if (first
.x() == second
.x())
528 return first
.y() < second
.y();
529 return first
.x() < second
.x();
532 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
533 if (first
.x() == second
.x())
534 return first
.bottom() > second
.bottom();
535 return first
.x() < second
.x();
538 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
539 if (first
.right() == second
.right())
540 return first
.y() < second
.y();
541 return first
.right() > second
.right();
544 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
545 if (first
.right() == second
.right())
546 return first
.bottom() > second
.bottom();
547 return first
.right() > second
.right();
551 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
553 spaces
.push_back(availableArea
); //initially the entire screen is free
556 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
557 end
= windowList
.end();
559 for (; wit
!= end
; ++wit
) {
560 const BlackboxWindow
* const curr
= *wit
;
562 if (curr
->isShaded()) continue;
564 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
565 curr
->frameRect().width() + screen
->getBorderWidth(),
566 curr
->frameRect().height() + screen
->getBorderWidth());
568 spaces
= calcSpace(tmp
, spaces
);
571 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
572 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
573 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
574 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
576 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
578 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
579 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
581 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
584 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
585 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
586 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
588 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
590 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
591 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
593 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
597 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
598 for(; sit
!= spaces_end
; ++sit
) {
599 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
603 if (sit
== spaces_end
)
606 //set new position based on the empty space found
607 const Rect
& where
= *sit
;
611 // adjust the location() based on left/right and top/bottom placement
612 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
613 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
614 win
.setX(where
.right() - win
.width());
615 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
616 win
.setY(where
.bottom() - win
.height());
618 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
619 win
.setY(win
.y() + where
.height() - win
.height());
620 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
621 win
.setX(win
.x() + where
.width() - win
.width());
627 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
631 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
632 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
633 x
= rx
- win
.width() / 2;
634 y
= ry
- win
.height() / 2;
636 if (x
< availableArea
.x())
637 x
= availableArea
.x();
638 if (y
< availableArea
.y())
639 y
= availableArea
.y();
640 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
641 x
= availableArea
.x() + availableArea
.width() - win
.width();
642 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
643 y
= availableArea
.y() + availableArea
.height() - win
.height();
652 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
653 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
654 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
655 cascade_x
= cascade_y
= 32;
657 if (cascade_x
== 32) {
658 cascade_x
+= availableArea
.x();
659 cascade_y
+= availableArea
.y();
662 win
.setPos(cascade_x
, cascade_y
);
668 void Workspace::placeWindow(BlackboxWindow
*win
) {
669 Rect
availableArea(screen
->availableArea()),
670 new_win(availableArea
.x(), availableArea
.y(),
671 win
->frameRect().width(), win
->frameRect().height());
674 switch (screen
->getPlacementPolicy()) {
675 case BScreen::RowSmartPlacement
:
676 case BScreen::ColSmartPlacement
:
677 placed
= smartPlacement(new_win
, availableArea
);
679 case BScreen::UnderMousePlacement
:
680 placed
= underMousePlacement(new_win
, availableArea
);
682 break; // handled below
685 if (placed
== False
) {
686 cascadePlacement(new_win
, availableArea
);
687 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
688 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
691 if (new_win
.right() > availableArea
.right())
692 new_win
.setX(availableArea
.left());
693 if (new_win
.bottom() > availableArea
.bottom())
694 new_win
.setY(availableArea
.top());
695 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());