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());