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