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