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();
109 (newfocus
->isIconic() || // do not focus icons
110 newfocus
->getWorkspaceNumber() != id
)) // or other workspaces
113 if (! newfocus
&& ! stackingList
.empty())
114 newfocus
= stackingList
.front();
116 assert(newfocus
!= w
); // this would be very wrong.
118 if (id
== screen
->getCurrentWorkspaceID()) {
120 if the window is on the visible workspace, then try focus it, and fall
121 back to the default focus target if the window won't focus.
123 if (! (newfocus
&& newfocus
->setInputFocus()))
124 screen
->getBlackbox()->setFocusedWindow(0);
125 } else if (lastfocus
== w
) {
127 If this workspace is not the current one do not assume that
128 w == lastfocus. If a sticky window is removed on a workspace other
129 than where it originated, it will fire the removeWindow on a
130 non-visible workspace.
134 If the window isn't on the visible workspace, don't focus the new one,
135 just mark it to be focused when the workspace comes into view.
137 setLastFocusedWindow(newfocus
);
141 windowList
.remove(w
);
142 clientmenu
->remove(w
->getWindowNumber());
143 clientmenu
->update();
145 screen
->updateNetizenWindowDel(w
->getClientWindow());
147 BlackboxWindowList::iterator it
= windowList
.begin();
148 const BlackboxWindowList::iterator end
= windowList
.end();
150 for (; it
!= end
; ++it
, ++i
)
151 (*it
)->setWindowNumber(i
);
154 cascade_x
= cascade_y
= 32;
160 void Workspace::showAll(void) {
161 std::for_each(stackingList
.begin(), stackingList
.end(),
162 std::mem_fun(&BlackboxWindow::show
));
166 void Workspace::hideAll(void) {
167 // withdraw in reverse order to minimize the number of Expose events
169 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
171 BlackboxWindowList::iterator it
= lst
.begin();
172 const BlackboxWindowList::iterator end
= lst
.end();
173 for (; it
!= end
; ++it
) {
174 BlackboxWindow
*bw
= *it
;
181 void Workspace::removeAll(void) {
182 while (! windowList
.empty())
183 windowList
.front()->iconify();
188 * returns the number of transients for win, plus the number of transients
189 * associated with each transient of win
191 static int countTransients(const BlackboxWindow
* const win
) {
192 int ret
= win
->getTransients().size();
194 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
195 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
196 ret
+= countTransients(*it
);
204 * puts the transients of win into the stack. windows are stacked above
205 * the window before it in the stackvector being iterated, meaning
206 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
209 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
210 StackVector::iterator
&stack
) {
211 if (win
->getTransients().size() == 0) return; // nothing to do
213 // put win's transients in the stack
214 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
215 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
216 *stack
++ = (*it
)->getFrameWindow();
217 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
219 if (! (*it
)->isIconic()) {
220 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
221 wkspc
->stackingList
.remove((*it
));
222 wkspc
->stackingList
.push_front((*it
));
226 // put transients of win's transients in the stack
227 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
228 raiseTransients(*it
, stack
);
233 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
234 StackVector::iterator
&stack
) {
235 if (win
->getTransients().size() == 0) return; // nothing to do
237 // put transients of win's transients in the stack
238 BlackboxWindowList::const_reverse_iterator it
,
239 end
= win
->getTransients().rend();
240 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
241 lowerTransients(*it
, stack
);
244 // put win's transients in the stack
245 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
246 *stack
++ = (*it
)->getFrameWindow();
247 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
249 if (! (*it
)->isIconic()) {
250 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
251 wkspc
->stackingList
.remove((*it
));
252 wkspc
->stackingList
.push_back((*it
));
258 void Workspace::raiseWindow(BlackboxWindow
*w
) {
259 BlackboxWindow
*win
= w
;
261 // walk up the transient_for's to the window that is not a transient
262 while (win
->isTransient()) {
263 if (! win
->getTransientFor()) break;
264 win
= win
->getTransientFor();
267 // get the total window count (win and all transients)
268 unsigned int i
= 1 + countTransients(win
);
270 // stack the window with all transients above
271 StackVector
stack_vector(i
);
272 StackVector::iterator stack
= stack_vector
.begin();
274 *(stack
++) = win
->getFrameWindow();
275 screen
->updateNetizenWindowRaise(win
->getClientWindow());
276 if (! win
->isIconic()) {
277 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
278 wkspc
->stackingList
.remove(win
);
279 wkspc
->stackingList
.push_front(win
);
282 raiseTransients(win
, stack
);
284 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
288 void Workspace::lowerWindow(BlackboxWindow
*w
) {
289 BlackboxWindow
*win
= w
;
291 // walk up the transient_for's to the window that is not a transient
292 while (win
->isTransient()) {
293 if (! win
->getTransientFor()) break;
294 win
= win
->getTransientFor();
297 // get the total window count (win and all transients)
298 unsigned int i
= 1 + countTransients(win
);
300 // stack the window with all transients above
301 StackVector
stack_vector(i
);
302 StackVector::iterator stack
= stack_vector
.begin();
304 lowerTransients(win
, stack
);
306 *(stack
++) = win
->getFrameWindow();
307 screen
->updateNetizenWindowLower(win
->getClientWindow());
308 if (! win
->isIconic()) {
309 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
310 wkspc
->stackingList
.remove(win
);
311 wkspc
->stackingList
.push_back(win
);
314 XLowerWindow(screen
->getBaseDisplay()->getXDisplay(), stack_vector
.front());
315 XRestackWindows(screen
->getBaseDisplay()->getXDisplay(),
316 &stack_vector
[0], stack_vector
.size());
317 screen
->lowerDesktops();
321 void Workspace::reconfigure(void) {
322 clientmenu
->reconfigure();
323 std::for_each(windowList
.begin(), windowList
.end(),
324 std::mem_fun(&BlackboxWindow::reconfigure
));
328 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
329 if (index
< windowList
.size()) {
330 BlackboxWindowList::iterator it
= windowList
.begin();
331 for(; index
> 0; --index
, ++it
); /* increment to index */
339 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
340 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
343 assert(it
!= windowList
.end()); // window must be in list
345 if (it
== windowList
.end())
346 return windowList
.front(); // if we walked off the end, wrap around
352 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
353 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
356 assert(it
!= windowList
.end()); // window must be in list
357 if (it
== windowList
.begin())
358 return windowList
.back(); // if we walked of the front, wrap around
364 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
365 return stackingList
.front();
369 void Workspace::sendWindowList(Netizen
&n
) {
370 BlackboxWindowList::iterator it
= windowList
.begin(),
371 end
= windowList
.end();
372 for(; it
!= end
; ++it
)
373 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
377 unsigned int Workspace::getCount(void) const {
378 return windowList
.size();
382 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
383 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
384 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
385 for (; it
!= end
; ++it
)
386 stack_order
.push_back(*it
);
390 bool Workspace::isCurrent(void) const {
391 return (id
== screen
->getCurrentWorkspaceID());
395 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
396 return (w
== windowList
.back());
400 void Workspace::setCurrent(void) {
401 screen
->changeWorkspaceID(id
);
405 void Workspace::setName(const string
& new_name
) {
406 if (! new_name
.empty()) {
409 // attempt to get from the _NET_WM_DESKTOP_NAMES property
410 XAtom::StringVect namesList
;
411 unsigned long numnames
= id
+ 1;
412 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
413 XAtom::utf8
, numnames
, namesList
) &&
414 namesList
.size() > id
) {
415 name
= namesList
[id
];
417 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
419 assert(tmp
.length() < 32);
420 char default_name
[32];
421 sprintf(default_name
, tmp
.c_str(), id
+ 1);
426 // reset the property with the new name
427 XAtom::StringVect namesList
;
428 unsigned long numnames
= (unsigned) -1;
429 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
430 XAtom::utf8
, numnames
, namesList
)) {
431 if (namesList
.size() > id
)
432 namesList
[id
] = name
;
434 namesList
.push_back(name
);
436 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
437 XAtom::utf8
, namesList
);
439 clientmenu
->setLabel(name
);
440 clientmenu
->update();
441 screen
->saveWorkspaceNames();
446 * Calculate free space available for window placement.
448 typedef std::vector
<Rect
> rectList
;
450 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
453 rectList::const_iterator siter
, end
= spaces
.end();
454 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
455 const Rect
&curr
= *siter
;
457 if(! win
.intersects(curr
)) {
458 result
.push_back(curr
);
462 /* Use an intersection of win and curr to determine the space around
463 * curr that we can use.
465 * NOTE: the spaces calculated can overlap.
470 extra
.setCoords(curr
.left(), curr
.top(),
471 isect
.left() - 1, curr
.bottom());
472 if (extra
.valid()) result
.push_back(extra
);
475 extra
.setCoords(curr
.left(), curr
.top(),
476 curr
.right(), isect
.top() - 1);
477 if (extra
.valid()) result
.push_back(extra
);
480 extra
.setCoords(isect
.right() + 1, curr
.top(),
481 curr
.right(), curr
.bottom());
482 if (extra
.valid()) result
.push_back(extra
);
485 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
486 curr
.right(), curr
.bottom());
487 if (extra
.valid()) result
.push_back(extra
);
493 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
494 if (first
.bottom() == second
.bottom())
495 return first
.right() > second
.right();
496 return first
.bottom() > second
.bottom();
499 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
500 if (first
.y() == second
.y())
501 return first
.right() > second
.right();
502 return first
.y() < second
.y();
505 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
506 if (first
.bottom() == second
.bottom())
507 return first
.x() < second
.x();
508 return first
.bottom() > second
.bottom();
511 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
512 if (first
.y() == second
.y())
513 return first
.x() < second
.x();
514 return first
.y() < second
.y();
517 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
518 if (first
.x() == second
.x())
519 return first
.y() < second
.y();
520 return first
.x() < second
.x();
523 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
524 if (first
.x() == second
.x())
525 return first
.bottom() > second
.bottom();
526 return first
.x() < second
.x();
529 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
530 if (first
.right() == second
.right())
531 return first
.y() < second
.y();
532 return first
.right() > second
.right();
535 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
536 if (first
.right() == second
.right())
537 return first
.bottom() > second
.bottom();
538 return first
.right() > second
.right();
542 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
544 spaces
.push_back(availableArea
); //initially the entire screen is free
547 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
548 end
= windowList
.end();
550 for (; wit
!= end
; ++wit
) {
551 const BlackboxWindow
* const curr
= *wit
;
553 if (curr
->isShaded()) continue;
555 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
556 curr
->frameRect().width() + screen
->getBorderWidth(),
557 curr
->frameRect().height() + screen
->getBorderWidth());
559 spaces
= calcSpace(tmp
, spaces
);
562 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
563 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
564 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
565 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
567 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
569 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
570 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
572 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
575 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
576 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
577 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
579 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
581 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
582 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
584 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
588 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
589 for(; sit
!= spaces_end
; ++sit
) {
590 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
594 if (sit
== spaces_end
)
597 //set new position based on the empty space found
598 const Rect
& where
= *sit
;
602 // adjust the location() based on left/right and top/bottom placement
603 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
604 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
605 win
.setX(where
.right() - win
.width());
606 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
607 win
.setY(where
.bottom() - win
.height());
609 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
610 win
.setY(win
.y() + where
.height() - win
.height());
611 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
612 win
.setX(win
.x() + where
.width() - win
.width());
618 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
622 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
623 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
624 x
= rx
- win
.width() / 2;
625 y
= ry
- win
.height() / 2;
627 if (x
< availableArea
.x())
628 x
= availableArea
.x();
629 if (y
< availableArea
.y())
630 y
= availableArea
.y();
631 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
632 x
= availableArea
.x() + availableArea
.width() - win
.width();
633 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
634 y
= availableArea
.y() + availableArea
.height() - win
.height();
643 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
644 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
645 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
646 cascade_x
= cascade_y
= 32;
648 if (cascade_x
== 32) {
649 cascade_x
+= availableArea
.x();
650 cascade_y
+= availableArea
.y();
653 win
.setPos(cascade_x
, cascade_y
);
659 void Workspace::placeWindow(BlackboxWindow
*win
) {
660 Rect
availableArea(screen
->availableArea()),
661 new_win(availableArea
.x(), availableArea
.y(),
662 win
->frameRect().width(), win
->frameRect().height());
665 switch (screen
->getPlacementPolicy()) {
666 case BScreen::RowSmartPlacement
:
667 case BScreen::ColSmartPlacement
:
668 placed
= smartPlacement(new_win
, availableArea
);
670 case BScreen::UnderMousePlacement
:
671 placed
= underMousePlacement(new_win
, availableArea
);
673 break; // handled below
676 if (placed
== False
) {
677 cascadePlacement(new_win
, availableArea
);
678 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
679 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
682 if (new_win
.right() > availableArea
.right())
683 new_win
.setX(availableArea
.left());
684 if (new_win
.bottom() > availableArea
.bottom())
685 new_win
.setY(availableArea
.top());
686 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());