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()) {
107 // if the window is sticky, then it needs to be removed on all other
110 for (unsigned int i
= 0; i
< screen
->getWorkspaceCount(); ++i
)
112 screen
->getWorkspace(i
)->focusFallback(w
);
116 windowList
.remove(w
);
117 clientmenu
->remove(w
->getWindowNumber());
118 clientmenu
->update();
120 screen
->updateNetizenWindowDel(w
->getClientWindow());
122 BlackboxWindowList::iterator it
= windowList
.begin();
123 const BlackboxWindowList::iterator end
= windowList
.end();
125 for (; it
!= end
; ++it
, ++i
)
126 (*it
)->setWindowNumber(i
);
129 cascade_x
= cascade_y
= 32;
135 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
136 BlackboxWindow
*newfocus
= 0;
138 if (id
== screen
->getCurrentWorkspaceID()) {
139 // The window is on the visible workspace.
141 // if it's a transient, then try to focus its parent
142 if (old_window
&& old_window
->isTransient()) {
143 newfocus
= old_window
->getTransientFor();
146 newfocus
->isIconic() || // do not focus icons
147 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
148 ! newfocus
->setInputFocus())
153 BlackboxWindowList::iterator it
= stackingList
.begin(),
154 end
= stackingList
.end();
155 for (; it
!= end
; ++it
) {
156 BlackboxWindow
*tmp
= *it
;
157 if (tmp
&& tmp
->setInputFocus()) {
158 // we found our new focus target
165 screen
->getBlackbox()->setFocusedWindow(newfocus
);
167 // The window is not on the visible workspace.
169 if (old_window
&& lastfocus
== old_window
) {
170 // The window was the last-focus target, so we need to replace it.
171 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
172 if (! stackingList
.empty())
173 win
= stackingList
.front();
174 setLastFocusedWindow(win
);
180 void Workspace::showAll(void) {
181 std::for_each(stackingList
.begin(), stackingList
.end(),
182 std::mem_fun(&BlackboxWindow::show
));
186 void Workspace::hideAll(void) {
187 // withdraw in reverse order to minimize the number of Expose events
189 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
191 BlackboxWindowList::iterator it
= lst
.begin();
192 const BlackboxWindowList::iterator end
= lst
.end();
193 for (; it
!= end
; ++it
) {
194 BlackboxWindow
*bw
= *it
;
201 void Workspace::removeAll(void) {
202 while (! windowList
.empty())
203 windowList
.front()->iconify();
208 * returns the number of transients for win, plus the number of transients
209 * associated with each transient of win
211 static int countTransients(const BlackboxWindow
* const win
) {
212 int ret
= win
->getTransients().size();
214 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
215 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
216 ret
+= countTransients(*it
);
224 * puts the transients of win into the stack. windows are stacked above
225 * the window before it in the stackvector being iterated, meaning
226 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
229 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
230 StackVector::iterator
&stack
) {
231 if (win
->getTransients().size() == 0) return; // nothing to do
233 // put win's transients in the stack
234 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
235 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
236 *stack
++ = (*it
)->getFrameWindow();
237 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
239 if (! (*it
)->isIconic()) {
240 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
241 wkspc
->stackingList
.remove((*it
));
242 wkspc
->stackingList
.push_front((*it
));
246 // put transients of win's transients in the stack
247 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
248 raiseTransients(*it
, stack
);
253 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
254 StackVector::iterator
&stack
) {
255 if (win
->getTransients().size() == 0) return; // nothing to do
257 // put transients of win's transients in the stack
258 BlackboxWindowList::const_reverse_iterator it
,
259 end
= win
->getTransients().rend();
260 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
261 lowerTransients(*it
, stack
);
264 // put win's transients in the stack
265 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
266 *stack
++ = (*it
)->getFrameWindow();
267 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
269 if (! (*it
)->isIconic()) {
270 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
271 wkspc
->stackingList
.remove((*it
));
272 wkspc
->stackingList
.push_back((*it
));
278 void Workspace::raiseWindow(BlackboxWindow
*w
) {
279 BlackboxWindow
*win
= w
;
281 // walk up the transient_for's to the window that is not a transient
282 while (win
->isTransient()) {
283 if (! win
->getTransientFor()) break;
284 win
= win
->getTransientFor();
287 // get the total window count (win and all transients)
288 unsigned int i
= 1 + countTransients(win
);
290 // stack the window with all transients above
291 StackVector
stack_vector(i
);
292 StackVector::iterator stack
= stack_vector
.begin();
294 *(stack
++) = win
->getFrameWindow();
295 screen
->updateNetizenWindowRaise(win
->getClientWindow());
296 if (! win
->isIconic()) {
297 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
298 wkspc
->stackingList
.remove(win
);
299 wkspc
->stackingList
.push_front(win
);
302 raiseTransients(win
, stack
);
304 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
308 void Workspace::lowerWindow(BlackboxWindow
*w
) {
309 BlackboxWindow
*win
= w
;
311 // walk up the transient_for's to the window that is not a transient
312 while (win
->isTransient()) {
313 if (! win
->getTransientFor()) break;
314 win
= win
->getTransientFor();
317 // get the total window count (win and all transients)
318 unsigned int i
= 1 + countTransients(win
);
320 // stack the window with all transients above
321 StackVector
stack_vector(i
);
322 StackVector::iterator stack
= stack_vector
.begin();
324 lowerTransients(win
, stack
);
326 *(stack
++) = win
->getFrameWindow();
327 screen
->updateNetizenWindowLower(win
->getClientWindow());
328 if (! win
->isIconic()) {
329 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
330 wkspc
->stackingList
.remove(win
);
331 wkspc
->stackingList
.push_back(win
);
334 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
338 void Workspace::reconfigure(void) {
339 clientmenu
->reconfigure();
340 std::for_each(windowList
.begin(), windowList
.end(),
341 std::mem_fun(&BlackboxWindow::reconfigure
));
345 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
346 if (index
< windowList
.size()) {
347 BlackboxWindowList::iterator it
= windowList
.begin();
348 for(; index
> 0; --index
, ++it
); /* increment to index */
356 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
357 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
360 assert(it
!= windowList
.end()); // window must be in list
362 if (it
== windowList
.end())
363 return windowList
.front(); // if we walked off the end, wrap around
369 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
370 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
373 assert(it
!= windowList
.end()); // window must be in list
374 if (it
== windowList
.begin())
375 return windowList
.back(); // if we walked of the front, wrap around
381 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
382 return stackingList
.front();
386 void Workspace::sendWindowList(Netizen
&n
) {
387 BlackboxWindowList::iterator it
= windowList
.begin(),
388 end
= windowList
.end();
389 for(; it
!= end
; ++it
)
390 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
394 unsigned int Workspace::getCount(void) const {
395 return windowList
.size();
399 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
400 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
401 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
402 for (; it
!= end
; ++it
)
403 stack_order
.push_back(*it
);
407 bool Workspace::isCurrent(void) const {
408 return (id
== screen
->getCurrentWorkspaceID());
412 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
413 return (w
== windowList
.back());
417 void Workspace::setCurrent(void) {
418 screen
->changeWorkspaceID(id
);
422 void Workspace::readName(void) {
423 XAtom::StringVect namesList
;
424 unsigned long numnames
= id
+ 1;
426 // attempt to get from the _NET_WM_DESKTOP_NAMES property
427 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
428 XAtom::utf8
, numnames
, namesList
) &&
429 namesList
.size() > id
) {
430 name
= namesList
[id
];
432 clientmenu
->setLabel(name
);
433 clientmenu
->update();
436 Use a default name. This doesn't actually change the class. That will
437 happen after the setName changes the root property, and that change
438 makes its way back to this function.
440 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
442 assert(tmp
.length() < 32);
443 char default_name
[32];
444 sprintf(default_name
, tmp
.c_str(), id
+ 1);
446 setName(default_name
); // save this into the _NET_WM_DESKTOP_NAMES property
451 void Workspace::setName(const string
& new_name
) {
452 // set the _NET_WM_DESKTOP_NAMES property with the new name
453 XAtom::StringVect namesList
;
454 unsigned long numnames
= (unsigned) -1;
455 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
456 XAtom::utf8
, numnames
, namesList
) &&
457 namesList
.size() > id
)
458 namesList
[id
] = new_name
;
460 namesList
.push_back(new_name
);
462 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
463 XAtom::utf8
, namesList
);
468 * Calculate free space available for window placement.
470 typedef std::vector
<Rect
> rectList
;
472 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
475 rectList::const_iterator siter
, end
= spaces
.end();
476 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
477 const Rect
&curr
= *siter
;
479 if(! win
.intersects(curr
)) {
480 result
.push_back(curr
);
484 /* Use an intersection of win and curr to determine the space around
485 * curr that we can use.
487 * NOTE: the spaces calculated can overlap.
492 extra
.setCoords(curr
.left(), curr
.top(),
493 isect
.left() - 1, curr
.bottom());
494 if (extra
.valid()) result
.push_back(extra
);
497 extra
.setCoords(curr
.left(), curr
.top(),
498 curr
.right(), isect
.top() - 1);
499 if (extra
.valid()) result
.push_back(extra
);
502 extra
.setCoords(isect
.right() + 1, curr
.top(),
503 curr
.right(), curr
.bottom());
504 if (extra
.valid()) result
.push_back(extra
);
507 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
508 curr
.right(), curr
.bottom());
509 if (extra
.valid()) result
.push_back(extra
);
515 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
516 if (first
.bottom() == second
.bottom())
517 return first
.right() > second
.right();
518 return first
.bottom() > second
.bottom();
521 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
522 if (first
.y() == second
.y())
523 return first
.right() > second
.right();
524 return first
.y() < second
.y();
527 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
528 if (first
.bottom() == second
.bottom())
529 return first
.x() < second
.x();
530 return first
.bottom() > second
.bottom();
533 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
534 if (first
.y() == second
.y())
535 return first
.x() < second
.x();
536 return first
.y() < second
.y();
539 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
540 if (first
.x() == second
.x())
541 return first
.y() < second
.y();
542 return first
.x() < second
.x();
545 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
546 if (first
.x() == second
.x())
547 return first
.bottom() > second
.bottom();
548 return first
.x() < second
.x();
551 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
552 if (first
.right() == second
.right())
553 return first
.y() < second
.y();
554 return first
.right() > second
.right();
557 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
558 if (first
.right() == second
.right())
559 return first
.bottom() > second
.bottom();
560 return first
.right() > second
.right();
564 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
566 spaces
.push_back(availableArea
); //initially the entire screen is free
569 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
570 end
= windowList
.end();
572 for (; wit
!= end
; ++wit
) {
573 const BlackboxWindow
* const curr
= *wit
;
575 if (curr
->isShaded()) continue;
577 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
578 curr
->frameRect().width() + screen
->getBorderWidth(),
579 curr
->frameRect().height() + screen
->getBorderWidth());
581 spaces
= calcSpace(tmp
, spaces
);
584 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
585 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
586 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
587 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
589 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
591 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
592 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
594 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
597 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
598 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
599 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
601 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
603 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
604 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
606 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
610 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
611 for(; sit
!= spaces_end
; ++sit
) {
612 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
616 if (sit
== spaces_end
)
619 //set new position based on the empty space found
620 const Rect
& where
= *sit
;
624 // adjust the location() based on left/right and top/bottom placement
625 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
626 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
627 win
.setX(where
.right() - win
.width());
628 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
629 win
.setY(where
.bottom() - win
.height());
631 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
632 win
.setY(win
.y() + where
.height() - win
.height());
633 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
634 win
.setX(win
.x() + where
.width() - win
.width());
640 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
644 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
645 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
646 x
= rx
- win
.width() / 2;
647 y
= ry
- win
.height() / 2;
649 if (x
< availableArea
.x())
650 x
= availableArea
.x();
651 if (y
< availableArea
.y())
652 y
= availableArea
.y();
653 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
654 x
= availableArea
.x() + availableArea
.width() - win
.width();
655 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
656 y
= availableArea
.y() + availableArea
.height() - win
.height();
665 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
666 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
667 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
668 cascade_x
= cascade_y
= 32;
670 if (cascade_x
== 32) {
671 cascade_x
+= availableArea
.x();
672 cascade_y
+= availableArea
.y();
675 win
.setPos(cascade_x
, cascade_y
);
681 void Workspace::placeWindow(BlackboxWindow
*win
) {
682 Rect
availableArea(screen
->availableArea()),
683 new_win(availableArea
.x(), availableArea
.y(),
684 win
->frameRect().width(), win
->frameRect().height());
687 switch (screen
->getPlacementPolicy()) {
688 case BScreen::RowSmartPlacement
:
689 case BScreen::ColSmartPlacement
:
690 placed
= smartPlacement(new_win
, availableArea
);
692 case BScreen::UnderMousePlacement
:
693 placed
= underMousePlacement(new_win
, availableArea
);
695 break; // handled below
698 if (placed
== False
) {
699 cascadePlacement(new_win
, availableArea
);
700 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
701 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
704 if (new_win
.right() > availableArea
.right())
705 new_win
.setX(availableArea
.left());
706 if (new_win
.bottom() > availableArea
.bottom())
707 new_win
.setY(availableArea
.top());
708 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());