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