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()) {
108 // if the window is sticky, then it needs to be removed on all other
111 for (unsigned int i
= 0; i
< screen
->getWorkspaceCount(); ++i
)
113 screen
->getWorkspace(i
)->focusFallback(w
);
117 windowList
.remove(w
);
118 clientmenu
->remove(w
->getWindowNumber());
119 clientmenu
->update();
121 screen
->updateNetizenWindowDel(w
->getClientWindow());
123 BlackboxWindowList::iterator it
= windowList
.begin();
124 const BlackboxWindowList::iterator end
= windowList
.end();
126 for (; it
!= end
; ++it
, ++i
)
127 (*it
)->setWindowNumber(i
);
130 cascade_x
= cascade_y
= 32;
136 void Workspace::focusFallback(const BlackboxWindow
*old_window
) {
137 BlackboxWindow
*newfocus
= 0;
139 if (id
== screen
->getCurrentWorkspaceID()) {
140 // The window is on the visible workspace.
142 // if it's a transient, then try to focus its parent
143 if (old_window
&& old_window
->isTransient()) {
144 newfocus
= old_window
->getTransientFor();
147 newfocus
->isIconic() || // do not focus icons
148 newfocus
->getWorkspaceNumber() != id
|| // or other workspaces
149 ! newfocus
->setInputFocus())
154 BlackboxWindowList::iterator it
= stackingList
.begin(),
155 end
= stackingList
.end();
156 for (; it
!= end
; ++it
) {
157 BlackboxWindow
*tmp
= *it
;
158 if (tmp
&& tmp
->setInputFocus()) {
159 // we found our new focus target
166 screen
->getBlackbox()->setFocusedWindow(newfocus
);
168 // The window is not on the visible workspace.
170 if (old_window
&& lastfocus
== old_window
) {
171 // The window was the last-focus target, so we need to replace it.
172 BlackboxWindow
*win
= (BlackboxWindow
*) 0;
173 if (! stackingList
.empty())
174 win
= stackingList
.front();
175 setLastFocusedWindow(win
);
181 void Workspace::showAll(void) {
182 std::for_each(stackingList
.begin(), stackingList
.end(),
183 std::mem_fun(&BlackboxWindow::show
));
187 void Workspace::hideAll(void) {
188 // withdraw in reverse order to minimize the number of Expose events
190 BlackboxWindowList
lst(stackingList
.rbegin(), stackingList
.rend());
192 BlackboxWindowList::iterator it
= lst
.begin();
193 const BlackboxWindowList::iterator end
= lst
.end();
194 for (; it
!= end
; ++it
) {
195 BlackboxWindow
*bw
= *it
;
202 void Workspace::removeAll(void) {
203 while (! windowList
.empty())
204 windowList
.front()->iconify();
209 * returns the number of transients for win, plus the number of transients
210 * associated with each transient of win
212 static int countTransients(const BlackboxWindow
* const win
) {
213 int ret
= win
->getTransients().size();
215 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
216 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
217 ret
+= countTransients(*it
);
225 * puts the transients of win into the stack. windows are stacked above
226 * the window before it in the stackvector being iterated, meaning
227 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
230 void Workspace::raiseTransients(const BlackboxWindow
* const win
,
231 StackVector::iterator
&stack
) {
232 if (win
->getTransients().size() == 0) return; // nothing to do
234 // put win's transients in the stack
235 BlackboxWindowList::const_iterator it
, end
= win
->getTransients().end();
236 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
237 *stack
++ = (*it
)->getFrameWindow();
238 screen
->updateNetizenWindowRaise((*it
)->getClientWindow());
240 if (! (*it
)->isIconic()) {
241 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
242 wkspc
->stackingList
.remove((*it
));
243 wkspc
->stackingList
.push_front((*it
));
247 // put transients of win's transients in the stack
248 for (it
= win
->getTransients().begin(); it
!= end
; ++it
) {
249 raiseTransients(*it
, stack
);
254 void Workspace::lowerTransients(const BlackboxWindow
* const win
,
255 StackVector::iterator
&stack
) {
256 if (win
->getTransients().size() == 0) return; // nothing to do
258 // put transients of win's transients in the stack
259 BlackboxWindowList::const_reverse_iterator it
,
260 end
= win
->getTransients().rend();
261 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
262 lowerTransients(*it
, stack
);
265 // put win's transients in the stack
266 for (it
= win
->getTransients().rbegin(); it
!= end
; ++it
) {
267 *stack
++ = (*it
)->getFrameWindow();
268 screen
->updateNetizenWindowLower((*it
)->getClientWindow());
270 if (! (*it
)->isIconic()) {
271 Workspace
*wkspc
= screen
->getWorkspace((*it
)->getWorkspaceNumber());
272 wkspc
->stackingList
.remove((*it
));
273 wkspc
->stackingList
.push_back((*it
));
279 void Workspace::raiseWindow(BlackboxWindow
*w
) {
280 BlackboxWindow
*win
= w
;
282 // walk up the transient_for's to the window that is not a transient
283 while (win
->isTransient()) {
284 if (! win
->getTransientFor()) break;
285 win
= win
->getTransientFor();
288 // get the total window count (win and all transients)
289 unsigned int i
= 1 + countTransients(win
);
291 // stack the window with all transients above
292 StackVector
stack_vector(i
);
293 StackVector::iterator stack
= stack_vector
.begin();
295 *(stack
++) = win
->getFrameWindow();
296 screen
->updateNetizenWindowRaise(win
->getClientWindow());
297 if (! win
->isIconic()) {
298 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
299 wkspc
->stackingList
.remove(win
);
300 wkspc
->stackingList
.push_front(win
);
303 raiseTransients(win
, stack
);
305 screen
->raiseWindows(&stack_vector
[0], stack_vector
.size());
309 void Workspace::lowerWindow(BlackboxWindow
*w
) {
310 BlackboxWindow
*win
= w
;
312 // walk up the transient_for's to the window that is not a transient
313 while (win
->isTransient()) {
314 if (! win
->getTransientFor()) break;
315 win
= win
->getTransientFor();
318 // get the total window count (win and all transients)
319 unsigned int i
= 1 + countTransients(win
);
321 // stack the window with all transients above
322 StackVector
stack_vector(i
);
323 StackVector::iterator stack
= stack_vector
.begin();
325 lowerTransients(win
, stack
);
327 *(stack
++) = win
->getFrameWindow();
328 screen
->updateNetizenWindowLower(win
->getClientWindow());
329 if (! win
->isIconic()) {
330 Workspace
*wkspc
= screen
->getWorkspace(win
->getWorkspaceNumber());
331 wkspc
->stackingList
.remove(win
);
332 wkspc
->stackingList
.push_back(win
);
335 screen
->lowerWindows(&stack_vector
[0], stack_vector
.size());
339 void Workspace::reconfigure(void) {
340 clientmenu
->reconfigure();
341 std::for_each(windowList
.begin(), windowList
.end(),
342 std::mem_fun(&BlackboxWindow::reconfigure
));
346 BlackboxWindow
*Workspace::getWindow(unsigned int index
) {
347 if (index
< windowList
.size()) {
348 BlackboxWindowList::iterator it
= windowList
.begin();
349 for(; index
> 0; --index
, ++it
); /* increment to index */
357 Workspace::getNextWindowInList(BlackboxWindow
*w
) {
358 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
361 assert(it
!= windowList
.end()); // window must be in list
363 if (it
== windowList
.end())
364 return windowList
.front(); // if we walked off the end, wrap around
370 BlackboxWindow
* Workspace::getPrevWindowInList(BlackboxWindow
*w
) {
371 BlackboxWindowList::iterator it
= std::find(windowList
.begin(),
374 assert(it
!= windowList
.end()); // window must be in list
375 if (it
== windowList
.begin())
376 return windowList
.back(); // if we walked of the front, wrap around
382 BlackboxWindow
* Workspace::getTopWindowOnStack(void) const {
383 return stackingList
.front();
387 void Workspace::sendWindowList(Netizen
&n
) {
388 BlackboxWindowList::iterator it
= windowList
.begin(),
389 end
= windowList
.end();
390 for(; it
!= end
; ++it
)
391 n
.sendWindowAdd((*it
)->getClientWindow(), getID());
395 unsigned int Workspace::getCount(void) const {
396 return windowList
.size();
400 void Workspace::appendStackOrder(BlackboxWindowList
&stack_order
) const {
401 BlackboxWindowList::const_reverse_iterator it
= stackingList
.rbegin();
402 const BlackboxWindowList::const_reverse_iterator end
= stackingList
.rend();
403 for (; it
!= end
; ++it
)
404 stack_order
.push_back(*it
);
408 bool Workspace::isCurrent(void) const {
409 return (id
== screen
->getCurrentWorkspaceID());
413 bool Workspace::isLastWindow(const BlackboxWindow
* const w
) const {
414 return (w
== windowList
.back());
418 void Workspace::setCurrent(void) {
419 screen
->changeWorkspaceID(id
);
423 void Workspace::setName(const string
& new_name
) {
424 if (! new_name
.empty()) {
427 // attempt to get from the _NET_WM_DESKTOP_NAMES property
428 XAtom::StringVect namesList
;
429 unsigned long numnames
= id
+ 1;
430 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
431 XAtom::utf8
, numnames
, namesList
) &&
432 namesList
.size() > id
) {
433 name
= namesList
[id
];
435 string tmp
=i18n(WorkspaceSet
, WorkspaceDefaultNameFormat
,
437 assert(tmp
.length() < 32);
438 char default_name
[32];
439 sprintf(default_name
, tmp
.c_str(), id
+ 1);
444 // reset the property with the new name
445 XAtom::StringVect namesList
;
446 unsigned long numnames
= (unsigned) -1;
447 if (xatom
->getValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
448 XAtom::utf8
, numnames
, namesList
) &&
449 namesList
.size() > id
)
450 namesList
[id
] = name
;
452 namesList
.push_back(name
);
454 xatom
->setValue(screen
->getRootWindow(), XAtom::net_desktop_names
,
455 XAtom::utf8
, namesList
);
457 clientmenu
->setLabel(name
);
458 clientmenu
->update();
459 screen
->saveWorkspaceNames();
464 * Calculate free space available for window placement.
466 typedef std::vector
<Rect
> rectList
;
468 static rectList
calcSpace(const Rect
&win
, const rectList
&spaces
) {
471 rectList::const_iterator siter
, end
= spaces
.end();
472 for (siter
= spaces
.begin(); siter
!= end
; ++siter
) {
473 const Rect
&curr
= *siter
;
475 if(! win
.intersects(curr
)) {
476 result
.push_back(curr
);
480 /* Use an intersection of win and curr to determine the space around
481 * curr that we can use.
483 * NOTE: the spaces calculated can overlap.
488 extra
.setCoords(curr
.left(), curr
.top(),
489 isect
.left() - 1, curr
.bottom());
490 if (extra
.valid()) result
.push_back(extra
);
493 extra
.setCoords(curr
.left(), curr
.top(),
494 curr
.right(), isect
.top() - 1);
495 if (extra
.valid()) result
.push_back(extra
);
498 extra
.setCoords(isect
.right() + 1, curr
.top(),
499 curr
.right(), curr
.bottom());
500 if (extra
.valid()) result
.push_back(extra
);
503 extra
.setCoords(curr
.left(), isect
.bottom() + 1,
504 curr
.right(), curr
.bottom());
505 if (extra
.valid()) result
.push_back(extra
);
511 static bool rowRLBT(const Rect
&first
, const Rect
&second
) {
512 if (first
.bottom() == second
.bottom())
513 return first
.right() > second
.right();
514 return first
.bottom() > second
.bottom();
517 static bool rowRLTB(const Rect
&first
, const Rect
&second
) {
518 if (first
.y() == second
.y())
519 return first
.right() > second
.right();
520 return first
.y() < second
.y();
523 static bool rowLRBT(const Rect
&first
, const Rect
&second
) {
524 if (first
.bottom() == second
.bottom())
525 return first
.x() < second
.x();
526 return first
.bottom() > second
.bottom();
529 static bool rowLRTB(const Rect
&first
, const Rect
&second
) {
530 if (first
.y() == second
.y())
531 return first
.x() < second
.x();
532 return first
.y() < second
.y();
535 static bool colLRTB(const Rect
&first
, const Rect
&second
) {
536 if (first
.x() == second
.x())
537 return first
.y() < second
.y();
538 return first
.x() < second
.x();
541 static bool colLRBT(const Rect
&first
, const Rect
&second
) {
542 if (first
.x() == second
.x())
543 return first
.bottom() > second
.bottom();
544 return first
.x() < second
.x();
547 static bool colRLTB(const Rect
&first
, const Rect
&second
) {
548 if (first
.right() == second
.right())
549 return first
.y() < second
.y();
550 return first
.right() > second
.right();
553 static bool colRLBT(const Rect
&first
, const Rect
&second
) {
554 if (first
.right() == second
.right())
555 return first
.bottom() > second
.bottom();
556 return first
.right() > second
.right();
560 bool Workspace::smartPlacement(Rect
& win
, const Rect
& availableArea
) {
562 spaces
.push_back(availableArea
); //initially the entire screen is free
565 BlackboxWindowList::const_iterator wit
= windowList
.begin(),
566 end
= windowList
.end();
568 for (; wit
!= end
; ++wit
) {
569 const BlackboxWindow
* const curr
= *wit
;
571 if (curr
->isShaded()) continue;
573 tmp
.setRect(curr
->frameRect().x(), curr
->frameRect().y(),
574 curr
->frameRect().width() + screen
->getBorderWidth(),
575 curr
->frameRect().height() + screen
->getBorderWidth());
577 spaces
= calcSpace(tmp
, spaces
);
580 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
581 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
) {
582 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
583 std::sort(spaces
.begin(), spaces
.end(), rowLRTB
);
585 std::sort(spaces
.begin(), spaces
.end(), rowLRBT
);
587 if(screen
->getColPlacementDirection() == BScreen::TopBottom
)
588 std::sort(spaces
.begin(), spaces
.end(), rowRLTB
);
590 std::sort(spaces
.begin(), spaces
.end(), rowRLBT
);
593 if(screen
->getColPlacementDirection() == BScreen::TopBottom
) {
594 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
595 std::sort(spaces
.begin(), spaces
.end(), colLRTB
);
597 std::sort(spaces
.begin(), spaces
.end(), colRLTB
);
599 if(screen
->getRowPlacementDirection() == BScreen::LeftRight
)
600 std::sort(spaces
.begin(), spaces
.end(), colLRBT
);
602 std::sort(spaces
.begin(), spaces
.end(), colRLBT
);
606 rectList::const_iterator sit
= spaces
.begin(), spaces_end
= spaces
.end();
607 for(; sit
!= spaces_end
; ++sit
) {
608 if (sit
->width() >= win
.width() && sit
->height() >= win
.height())
612 if (sit
== spaces_end
)
615 //set new position based on the empty space found
616 const Rect
& where
= *sit
;
620 // adjust the location() based on left/right and top/bottom placement
621 if (screen
->getPlacementPolicy() == BScreen::RowSmartPlacement
) {
622 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
623 win
.setX(where
.right() - win
.width());
624 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
625 win
.setY(where
.bottom() - win
.height());
627 if (screen
->getColPlacementDirection() == BScreen::BottomTop
)
628 win
.setY(win
.y() + where
.height() - win
.height());
629 if (screen
->getRowPlacementDirection() == BScreen::RightLeft
)
630 win
.setX(win
.x() + where
.width() - win
.width());
636 bool Workspace::underMousePlacement(Rect
&win
, const Rect
&availableArea
) {
640 XQueryPointer(screen
->getBlackbox()->getXDisplay(), screen
->getRootWindow(),
641 &r
, &c
, &rx
, &ry
, &x
, &y
, &m
);
642 x
= rx
- win
.width() / 2;
643 y
= ry
- win
.height() / 2;
645 if (x
< availableArea
.x())
646 x
= availableArea
.x();
647 if (y
< availableArea
.y())
648 y
= availableArea
.y();
649 if (x
+ win
.width() > availableArea
.x() + availableArea
.width())
650 x
= availableArea
.x() + availableArea
.width() - win
.width();
651 if (y
+ win
.height() > availableArea
.y() + availableArea
.height())
652 y
= availableArea
.y() + availableArea
.height() - win
.height();
661 bool Workspace::cascadePlacement(Rect
&win
, const Rect
&availableArea
) {
662 if ((cascade_x
> static_cast<signed>(availableArea
.width() / 2)) ||
663 (cascade_y
> static_cast<signed>(availableArea
.height() / 2)))
664 cascade_x
= cascade_y
= 32;
666 if (cascade_x
== 32) {
667 cascade_x
+= availableArea
.x();
668 cascade_y
+= availableArea
.y();
671 win
.setPos(cascade_x
, cascade_y
);
677 void Workspace::placeWindow(BlackboxWindow
*win
) {
678 Rect
availableArea(screen
->availableArea()),
679 new_win(availableArea
.x(), availableArea
.y(),
680 win
->frameRect().width(), win
->frameRect().height());
683 switch (screen
->getPlacementPolicy()) {
684 case BScreen::RowSmartPlacement
:
685 case BScreen::ColSmartPlacement
:
686 placed
= smartPlacement(new_win
, availableArea
);
688 case BScreen::UnderMousePlacement
:
689 placed
= underMousePlacement(new_win
, availableArea
);
691 break; // handled below
694 if (placed
== False
) {
695 cascadePlacement(new_win
, availableArea
);
696 cascade_x
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
697 cascade_y
+= win
->getTitleHeight() + (screen
->getBorderWidth() * 2);
700 if (new_win
.right() > availableArea
.right())
701 new_win
.setX(availableArea
.left());
702 if (new_win
.bottom() > availableArea
.bottom())
703 new_win
.setY(availableArea
.top());
704 win
->configure(new_win
.x(), new_win
.y(), new_win
.width(), new_win
.height());