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