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