]> icculus.org git repositories - mikachu/openbox.git/blob - src/Workspace.cc
properly lower windows and keep desktop windows on the bottom.
[mikachu/openbox.git] / src / Workspace.cc
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)
5 //
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:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
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.
23
24 #ifdef    HAVE_CONFIG_H
25 #  include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31
32 #ifdef    HAVE_STDIO_H
33 #  include <stdio.h>
34 #endif // HAVE_STDIO_H
35
36 #ifdef HAVE_STRING_H
37 #  include <string.h>
38 #endif // HAVE_STRING_H
39 }
40
41 #include <assert.h>
42
43 #include <functional>
44 #include <string>
45
46 using std::string;
47
48 #include "i18n.hh"
49 #include "blackbox.hh"
50 #include "Clientmenu.hh"
51 #include "Netizen.hh"
52 #include "Screen.hh"
53 #include "Toolbar.hh"
54 #include "Util.hh"
55 #include "Window.hh"
56 #include "Workspace.hh"
57 #include "Windowmenu.hh"
58 #include "XAtom.hh"
59
60
61 Workspace::Workspace(BScreen *scrn, unsigned int i) {
62   screen = scrn;
63   xatom = screen->getBlackbox()->getXAtom();
64
65   cascade_x = cascade_y = 32;
66
67   id = i;
68
69   clientmenu = new Clientmenu(this);
70
71   lastfocus = (BlackboxWindow *) 0;
72
73   setName("");
74 }
75
76
77 void Workspace::addWindow(BlackboxWindow *w, bool place) {
78   assert(w != 0);
79
80   if (place) placeWindow(w);
81
82   w->setWorkspace(id);
83   w->setWindowNumber(windowList.size());
84
85   stackingList.push_front(w);
86   windowList.push_back(w);
87
88   clientmenu->insert(w->getTitle());
89   clientmenu->update();
90
91   screen->updateNetizenWindowAdd(w->getClientWindow(), id);
92
93   raiseWindow(w);
94 }
95
96
97 unsigned int Workspace::removeWindow(BlackboxWindow *w) {
98   assert(w != 0);
99
100   stackingList.remove(w);
101
102   // pass focus to the next appropriate window
103   if ((w->isFocused() || w == lastfocus) &&
104       ! screen->getBlackbox()->doShutdown()) {
105     if (id == screen->getCurrentWorkspaceID()) {
106       // The window is on the visible workspace
107       focusFallback(w);
108     } else {
109       // The window is not on the visible workspace.
110       if (lastfocus == w) {
111         // The window was the last-focus target, so we need to replace it.
112         setLastFocusedWindow(stackingList.front());
113       }
114       // if the window focused on the current workspace, then reapply that
115       // workspace's focus too
116       if (w->isFocused())
117         screen->getCurrentWorkspace()->focusFallback(w);
118     }
119   }
120
121   windowList.remove(w);
122   clientmenu->remove(w->getWindowNumber());
123   clientmenu->update();
124
125   screen->updateNetizenWindowDel(w->getClientWindow());
126
127   BlackboxWindowList::iterator it = windowList.begin();
128   const BlackboxWindowList::iterator end = windowList.end();
129   unsigned int i = 0;
130   for (; it != end; ++it, ++i)
131     (*it)->setWindowNumber(i);
132
133   if (i == 0)
134     cascade_x = cascade_y = 32;
135
136   return i;
137 }
138
139
140 void Workspace::focusFallback(const BlackboxWindow *old_window) {
141   BlackboxWindow *newfocus = 0;
142
143   // if it's a transient, then try to focus its parent
144   if (old_window && old_window->isTransient()) {
145     newfocus = old_window->getTransientFor();
146
147     if (! newfocus ||
148         newfocus->isIconic() ||                  // do not focus icons
149         newfocus->getWorkspaceNumber() != id ||  // or other workspaces
150         ! newfocus->setInputFocus())
151       newfocus = 0;
152   }
153
154   if (! newfocus) {
155     BlackboxWindowList::iterator it = stackingList.begin(),
156                                 end = stackingList.end();
157     for (; it != end; ++it) {
158       BlackboxWindow *tmp = *it;
159       if (tmp && tmp->setInputFocus()) {
160         // we found our new focus target
161         newfocus = tmp;
162         break;
163       }
164     }
165   }
166
167   screen->getBlackbox()->setFocusedWindow(newfocus);
168 }
169
170
171 void Workspace::showAll(void) {
172   std::for_each(stackingList.begin(), stackingList.end(),
173                 std::mem_fun(&BlackboxWindow::show));
174 }
175
176
177 void Workspace::hideAll(void) {
178   // withdraw in reverse order to minimize the number of Expose events
179
180   BlackboxWindowList lst(stackingList.rbegin(), stackingList.rend());
181
182   BlackboxWindowList::iterator it = lst.begin();
183   const BlackboxWindowList::iterator end = lst.end();
184   for (; it != end; ++it) {
185     BlackboxWindow *bw = *it;
186     if (! bw->isStuck())
187       bw->withdraw();
188   }
189 }
190
191
192 void Workspace::removeAll(void) {
193   while (! windowList.empty())
194     windowList.front()->iconify();
195 }
196
197
198 /*
199  * returns the number of transients for win, plus the number of transients
200  * associated with each transient of win
201  */
202 static int countTransients(const BlackboxWindow * const win) {
203   int ret = win->getTransients().size();
204   if (ret > 0) {
205     BlackboxWindowList::const_iterator it, end = win->getTransients().end();
206     for (it = win->getTransients().begin(); it != end; ++it) {
207       ret += countTransients(*it);
208     }
209   }
210   return ret;
211 }
212
213
214 /*
215  * puts the transients of win into the stack. windows are stacked above
216  * the window before it in the stackvector being iterated, meaning
217  * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
218  * stack[1], etc...
219  */
220 void Workspace::raiseTransients(const BlackboxWindow * const win,
221                                 StackVector::iterator &stack) {
222   if (win->getTransients().size() == 0) return; // nothing to do
223
224   // put win's transients in the stack
225   BlackboxWindowList::const_iterator it, end = win->getTransients().end();
226   for (it = win->getTransients().begin(); it != end; ++it) {
227     *stack++ = (*it)->getFrameWindow();
228     screen->updateNetizenWindowRaise((*it)->getClientWindow());
229
230     if (! (*it)->isIconic()) {
231       Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
232       wkspc->stackingList.remove((*it));
233       wkspc->stackingList.push_front((*it));
234     }
235   }
236
237   // put transients of win's transients in the stack
238   for (it = win->getTransients().begin(); it != end; ++it) {
239     raiseTransients(*it, stack);
240   }
241 }
242
243
244 void Workspace::lowerTransients(const BlackboxWindow * const win,
245                                 StackVector::iterator &stack) {
246   if (win->getTransients().size() == 0) return; // nothing to do
247
248   // put transients of win's transients in the stack
249   BlackboxWindowList::const_reverse_iterator it,
250     end = win->getTransients().rend();
251   for (it = win->getTransients().rbegin(); it != end; ++it) {
252     lowerTransients(*it, stack);
253   }
254
255   // put win's transients in the stack
256   for (it = win->getTransients().rbegin(); it != end; ++it) {
257     *stack++ = (*it)->getFrameWindow();
258     screen->updateNetizenWindowLower((*it)->getClientWindow());
259
260     if (! (*it)->isIconic()) {
261       Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
262       wkspc->stackingList.remove((*it));
263       wkspc->stackingList.push_back((*it));
264     }
265   }
266 }
267
268
269 void Workspace::raiseWindow(BlackboxWindow *w) {
270   BlackboxWindow *win = w;
271
272   // walk up the transient_for's to the window that is not a transient
273   while (win->isTransient()) {
274     if (! win->getTransientFor()) break;
275     win = win->getTransientFor();
276   }
277
278   // get the total window count (win and all transients)
279   unsigned int i = 1 + countTransients(win);
280
281   // stack the window with all transients above
282   StackVector stack_vector(i);
283   StackVector::iterator stack = stack_vector.begin();
284
285   *(stack++) = win->getFrameWindow();
286   screen->updateNetizenWindowRaise(win->getClientWindow());
287   if (! win->isIconic()) {
288     Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
289     wkspc->stackingList.remove(win);
290     wkspc->stackingList.push_front(win);
291   }
292
293   raiseTransients(win, stack);
294
295   screen->raiseWindows(&stack_vector[0], stack_vector.size());
296 }
297
298
299 void Workspace::lowerWindow(BlackboxWindow *w) {
300   BlackboxWindow *win = w;
301
302   // walk up the transient_for's to the window that is not a transient
303   while (win->isTransient()) {
304     if (! win->getTransientFor()) break;
305     win = win->getTransientFor();
306   }
307
308   // get the total window count (win and all transients)
309   unsigned int i = 1 + countTransients(win);
310
311   // stack the window with all transients above
312   StackVector stack_vector(i);
313   StackVector::iterator stack = stack_vector.begin();
314
315   lowerTransients(win, stack);
316
317   *(stack++) = win->getFrameWindow();
318   screen->updateNetizenWindowLower(win->getClientWindow());
319   if (! win->isIconic()) {
320     Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
321     wkspc->stackingList.remove(win);
322     wkspc->stackingList.push_back(win);
323   }
324
325   screen->lowerWindows(&stack_vector[0], stack_vector.size());
326 }
327
328
329 void Workspace::reconfigure(void) {
330   clientmenu->reconfigure();
331   std::for_each(windowList.begin(), windowList.end(),
332                 std::mem_fun(&BlackboxWindow::reconfigure));
333 }
334
335
336 BlackboxWindow *Workspace::getWindow(unsigned int index) {
337   if (index < windowList.size()) {
338     BlackboxWindowList::iterator it = windowList.begin();
339     for(; index > 0; --index, ++it); /* increment to index */
340     return *it;
341   }
342   return 0;
343 }
344
345
346 BlackboxWindow*
347 Workspace::getNextWindowInList(BlackboxWindow *w) {
348   BlackboxWindowList::iterator it = std::find(windowList.begin(),
349                                               windowList.end(),
350                                               w);
351   assert(it != windowList.end());   // window must be in list
352   ++it;                             // next window
353   if (it == windowList.end())
354     return windowList.front();      // if we walked off the end, wrap around
355
356   return *it;
357 }
358
359
360 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
361   BlackboxWindowList::iterator it = std::find(windowList.begin(),
362                                               windowList.end(),
363                                               w);
364   assert(it != windowList.end()); // window must be in list
365   if (it == windowList.begin())
366     return windowList.back();     // if we walked of the front, wrap around
367
368   return *(--it);
369 }
370
371
372 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
373   return stackingList.front();
374 }
375
376
377 void Workspace::sendWindowList(Netizen &n) {
378   BlackboxWindowList::iterator it = windowList.begin(),
379     end = windowList.end();
380   for(; it != end; ++it)
381     n.sendWindowAdd((*it)->getClientWindow(), getID());
382 }
383
384
385 unsigned int Workspace::getCount(void) const {
386   return windowList.size();
387 }
388
389
390 void Workspace::appendStackOrder(BlackboxWindowList &stack_order) const {
391   BlackboxWindowList::const_reverse_iterator it = stackingList.rbegin();
392   const BlackboxWindowList::const_reverse_iterator end = stackingList.rend();
393   for (; it != end; ++it)
394     stack_order.push_back(*it);
395 }
396   
397
398 bool Workspace::isCurrent(void) const {
399   return (id == screen->getCurrentWorkspaceID());
400 }
401
402
403 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
404   return (w == windowList.back());
405 }
406
407
408 void Workspace::setCurrent(void) {
409   screen->changeWorkspaceID(id);
410 }
411
412
413 void Workspace::setName(const string& new_name) {
414   if (! new_name.empty()) {
415     name = new_name;
416   } else {
417     // attempt to get from the _NET_WM_DESKTOP_NAMES property
418     XAtom::StringVect namesList;
419     unsigned long numnames = id + 1;
420     if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
421                         XAtom::utf8, numnames, namesList) &&
422         namesList.size() > id) {
423       name = namesList[id];
424     } else {
425       string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat,
426                        "Workspace %d");
427       assert(tmp.length() < 32);
428       char default_name[32];
429       sprintf(default_name, tmp.c_str(), id + 1);
430       name = default_name;
431     }
432   }
433   
434   // reset the property with the new name
435   XAtom::StringVect namesList;
436   unsigned long numnames = (unsigned) -1;
437   if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
438                       XAtom::utf8, numnames, namesList)) {
439     if (namesList.size() > id)
440       namesList[id] = name;
441     else
442       namesList.push_back(name);
443   }
444   xatom->setValue(screen->getRootWindow(), XAtom::net_desktop_names,
445                   XAtom::utf8, namesList);
446
447   clientmenu->setLabel(name);
448   clientmenu->update();
449   screen->saveWorkspaceNames();
450 }
451
452
453 /*
454  * Calculate free space available for window placement.
455  */
456 typedef std::vector<Rect> rectList;
457
458 static rectList calcSpace(const Rect &win, const rectList &spaces) {
459   Rect isect, extra;
460   rectList result;
461   rectList::const_iterator siter, end = spaces.end();
462   for (siter = spaces.begin(); siter != end; ++siter) {
463     const Rect &curr = *siter;
464
465     if(! win.intersects(curr)) {
466       result.push_back(curr);
467       continue;
468     }
469
470     /* Use an intersection of win and curr to determine the space around
471      * curr that we can use.
472      *
473      * NOTE: the spaces calculated can overlap.
474      */
475     isect = curr & win;
476
477     // left
478     extra.setCoords(curr.left(), curr.top(),
479                     isect.left() - 1, curr.bottom());
480     if (extra.valid()) result.push_back(extra);
481
482     // top
483     extra.setCoords(curr.left(), curr.top(),
484                     curr.right(), isect.top() - 1);
485     if (extra.valid()) result.push_back(extra);
486
487     // right
488     extra.setCoords(isect.right() + 1, curr.top(),
489                     curr.right(), curr.bottom());
490     if (extra.valid()) result.push_back(extra);
491
492     // bottom
493     extra.setCoords(curr.left(), isect.bottom() + 1,
494                     curr.right(), curr.bottom());
495     if (extra.valid()) result.push_back(extra);
496   }
497   return result;
498 }
499
500
501 static bool rowRLBT(const Rect &first, const Rect &second) {
502   if (first.bottom() == second.bottom())
503     return first.right() > second.right();
504   return first.bottom() > second.bottom();
505 }
506
507 static bool rowRLTB(const Rect &first, const Rect &second) {
508   if (first.y() == second.y())
509     return first.right() > second.right();
510   return first.y() < second.y();
511 }
512
513 static bool rowLRBT(const Rect &first, const Rect &second) {
514   if (first.bottom() == second.bottom())
515     return first.x() < second.x();
516   return first.bottom() > second.bottom();
517 }
518
519 static bool rowLRTB(const Rect &first, const Rect &second) {
520   if (first.y() == second.y())
521     return first.x() < second.x();
522   return first.y() < second.y();
523 }
524
525 static bool colLRTB(const Rect &first, const Rect &second) {
526   if (first.x() == second.x())
527     return first.y() < second.y();
528   return first.x() < second.x();
529 }
530
531 static bool colLRBT(const Rect &first, const Rect &second) {
532   if (first.x() == second.x())
533     return first.bottom() > second.bottom();
534   return first.x() < second.x();
535 }
536
537 static bool colRLTB(const Rect &first, const Rect &second) {
538   if (first.right() == second.right())
539     return first.y() < second.y();
540   return first.right() > second.right();
541 }
542
543 static bool colRLBT(const Rect &first, const Rect &second) {
544   if (first.right() == second.right())
545     return first.bottom() > second.bottom();
546   return first.right() > second.right();
547 }
548
549
550 bool Workspace::smartPlacement(Rect& win, const Rect& availableArea) {
551   rectList spaces;
552   spaces.push_back(availableArea); //initially the entire screen is free
553
554   //Find Free Spaces
555   BlackboxWindowList::const_iterator wit = windowList.begin(),
556     end = windowList.end();
557   Rect tmp;
558   for (; wit != end; ++wit) {
559     const BlackboxWindow* const curr = *wit;
560
561     if (curr->isShaded()) continue;
562
563     tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
564                 curr->frameRect().width() + screen->getBorderWidth(),
565                 curr->frameRect().height() + screen->getBorderWidth());
566
567     spaces = calcSpace(tmp, spaces);
568   }
569
570   if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
571     if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
572       if(screen->getColPlacementDirection() == BScreen::TopBottom)
573         std::sort(spaces.begin(), spaces.end(), rowLRTB);
574       else
575         std::sort(spaces.begin(), spaces.end(), rowLRBT);
576     } else {
577       if(screen->getColPlacementDirection() == BScreen::TopBottom)
578         std::sort(spaces.begin(), spaces.end(), rowRLTB);
579       else
580         std::sort(spaces.begin(), spaces.end(), rowRLBT);
581     }
582   } else {
583     if(screen->getColPlacementDirection() == BScreen::TopBottom) {
584       if(screen->getRowPlacementDirection() == BScreen::LeftRight)
585         std::sort(spaces.begin(), spaces.end(), colLRTB);
586       else
587         std::sort(spaces.begin(), spaces.end(), colRLTB);
588     } else {
589       if(screen->getRowPlacementDirection() == BScreen::LeftRight)
590         std::sort(spaces.begin(), spaces.end(), colLRBT);
591       else
592         std::sort(spaces.begin(), spaces.end(), colRLBT);
593     }
594   }
595
596   rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
597   for(; sit != spaces_end; ++sit) {
598     if (sit->width() >= win.width() && sit->height() >= win.height())
599       break;
600   }
601
602   if (sit == spaces_end)
603     return False;
604
605   //set new position based on the empty space found
606   const Rect& where = *sit;
607   win.setX(where.x());
608   win.setY(where.y());
609
610   // adjust the location() based on left/right and top/bottom placement
611   if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
612     if (screen->getRowPlacementDirection() == BScreen::RightLeft)
613       win.setX(where.right() - win.width());
614     if (screen->getColPlacementDirection() == BScreen::BottomTop)
615       win.setY(where.bottom() - win.height());
616   } else {
617     if (screen->getColPlacementDirection() == BScreen::BottomTop)
618       win.setY(win.y() + where.height() - win.height());
619     if (screen->getRowPlacementDirection() == BScreen::RightLeft)
620       win.setX(win.x() + where.width() - win.width());
621   }
622   return True;
623 }
624
625
626 bool Workspace::underMousePlacement(Rect &win, const Rect &availableArea) {
627   int x, y, rx, ry;
628   Window c, r;
629   unsigned int m;
630   XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
631                 &r, &c, &rx, &ry, &x, &y, &m);
632   x = rx - win.width() / 2;
633   y = ry - win.height() / 2;
634
635   if (x < availableArea.x())
636     x = availableArea.x();
637   if (y < availableArea.y())
638     y = availableArea.y();
639   if (x + win.width() > availableArea.x() + availableArea.width())
640     x = availableArea.x() + availableArea.width() - win.width();
641   if (y + win.height() > availableArea.y() + availableArea.height())
642     y = availableArea.y() + availableArea.height() - win.height();
643
644   win.setX(x);
645   win.setY(y);
646
647   return True;
648 }
649
650
651 bool Workspace::cascadePlacement(Rect &win, const Rect &availableArea) {
652   if ((cascade_x > static_cast<signed>(availableArea.width() / 2)) ||
653       (cascade_y > static_cast<signed>(availableArea.height() / 2)))
654     cascade_x = cascade_y = 32;
655
656   if (cascade_x == 32) {
657     cascade_x += availableArea.x();
658     cascade_y += availableArea.y();
659   }
660
661   win.setPos(cascade_x, cascade_y);
662
663   return True;
664 }
665
666
667 void Workspace::placeWindow(BlackboxWindow *win) {
668   Rect availableArea(screen->availableArea()),
669     new_win(availableArea.x(), availableArea.y(),
670             win->frameRect().width(), win->frameRect().height());
671   bool placed = False;
672
673   switch (screen->getPlacementPolicy()) {
674   case BScreen::RowSmartPlacement:
675   case BScreen::ColSmartPlacement:
676     placed = smartPlacement(new_win, availableArea);
677     break;
678   case BScreen::UnderMousePlacement:
679     placed = underMousePlacement(new_win, availableArea);
680   default:
681     break; // handled below
682   } // switch
683
684   if (placed == False) {
685     cascadePlacement(new_win, availableArea);
686     cascade_x += win->getTitleHeight() + (screen->getBorderWidth() * 2);
687     cascade_y += win->getTitleHeight() + (screen->getBorderWidth() * 2);
688   }
689
690   if (new_win.right() > availableArea.right())
691     new_win.setX(availableArea.left());
692   if (new_win.bottom() > availableArea.bottom())
693     new_win.setY(availableArea.top());
694   win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());
695 }