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