]> Dogcows Code - chaz/openbox/blob - src/Workspace.cc
add options for ignoring shaded windows and full-maxed windows while placing new...
[chaz/openbox] / src / Workspace.cc
1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Workspace.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2002 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000 Brad Hughes (bhughes@tcac.net)
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a
7 // copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the
11 // Software is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23
24 #ifdef HAVE_CONFIG_H
25 # include "../config.h"
26 #endif // HAVE_CONFIG_H
27
28 extern "C" {
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31
32 #ifdef HAVE_STDIO_H
33 # include <stdio.h>
34 #endif // HAVE_STDIO_H
35
36 #ifdef HAVE_STRING_H
37 # include <string.h>
38 #endif // HAVE_STRING_H
39 }
40
41 #include <assert.h>
42
43 #include <functional>
44 #include <string>
45
46 using std::string;
47
48 #include "i18n.hh"
49 #include "blackbox.hh"
50 #include "Clientmenu.hh"
51 #include "Netizen.hh"
52 #include "Screen.hh"
53 #include "Toolbar.hh"
54 #include "Util.hh"
55 #include "Window.hh"
56 #include "Workspace.hh"
57 #include "Windowmenu.hh"
58 #include "XAtom.hh"
59
60
61 Workspace::Workspace(BScreen *scrn, unsigned int i) {
62 screen = scrn;
63 xatom = screen->getBlackbox()->getXAtom();
64
65 cascade_x = cascade_y = 32;
66
67 id = i;
68
69 clientmenu = new Clientmenu(this);
70
71 lastfocus = (BlackboxWindow *) 0;
72
73 readName();
74 }
75
76
77 void Workspace::addWindow(BlackboxWindow *w, bool place) {
78 assert(w != 0);
79
80 if (place) placeWindow(w);
81
82 w->setWorkspace(id);
83 w->setWindowNumber(windowList.size());
84
85 stackingList.push_front(w);
86 windowList.push_back(w);
87
88 clientmenu->insert(w->getTitle());
89 clientmenu->update();
90
91 screen->updateNetizenWindowAdd(w->getClientWindow(), id);
92
93 raiseWindow(w);
94 }
95
96
97 unsigned int Workspace::removeWindow(BlackboxWindow *w) {
98 assert(w != 0);
99
100 stackingList.remove(w);
101
102 // pass focus to the next appropriate window
103 if ((w->isFocused() || w == lastfocus) &&
104 ! screen->getBlackbox()->doShutdown()) {
105 focusFallback(w);
106
107 // if the window is sticky, then it needs to be removed on all other
108 // workspaces too!
109 if (w->isStuck()) {
110 for (unsigned int i = 0; i < screen->getWorkspaceCount(); ++i)
111 if (i != id)
112 screen->getWorkspace(i)->focusFallback(w);
113 }
114 }
115
116 windowList.remove(w);
117 clientmenu->remove(w->getWindowNumber());
118 clientmenu->update();
119
120 screen->updateNetizenWindowDel(w->getClientWindow());
121
122 BlackboxWindowList::iterator it = windowList.begin();
123 const BlackboxWindowList::iterator end = windowList.end();
124 unsigned int i = 0;
125 for (; it != end; ++it, ++i)
126 (*it)->setWindowNumber(i);
127
128 if (i == 0)
129 cascade_x = cascade_y = 32;
130
131 return i;
132 }
133
134
135 void Workspace::focusFallback(const BlackboxWindow *old_window) {
136 BlackboxWindow *newfocus = 0;
137
138 if (id == screen->getCurrentWorkspaceID()) {
139 // The window is on the visible workspace.
140
141 // if it's a transient, then try to focus its parent
142 if (old_window && old_window->isTransient()) {
143 newfocus = old_window->getTransientFor();
144
145 if (! newfocus ||
146 newfocus->isIconic() || // do not focus icons
147 newfocus->getWorkspaceNumber() != id || // or other workspaces
148 ! newfocus->setInputFocus())
149 newfocus = 0;
150 }
151
152 if (! newfocus) {
153 BlackboxWindowList::iterator it = stackingList.begin(),
154 end = stackingList.end();
155 for (; it != end; ++it) {
156 BlackboxWindow *tmp = *it;
157 if (tmp && tmp->setInputFocus()) {
158 // we found our new focus target
159 newfocus = tmp;
160 break;
161 }
162 }
163 }
164
165 screen->getBlackbox()->setFocusedWindow(newfocus);
166 } else {
167 // The window is not on the visible workspace.
168
169 if (old_window && lastfocus == old_window) {
170 // The window was the last-focus target, so we need to replace it.
171 BlackboxWindow *win = (BlackboxWindow*) 0;
172 if (! stackingList.empty())
173 win = stackingList.front();
174 setLastFocusedWindow(win);
175 }
176 }
177 }
178
179
180 void Workspace::showAll(void) {
181 std::for_each(stackingList.begin(), stackingList.end(),
182 std::mem_fun(&BlackboxWindow::show));
183 }
184
185
186 void Workspace::hideAll(void) {
187 // withdraw in reverse order to minimize the number of Expose events
188
189 BlackboxWindowList lst(stackingList.rbegin(), stackingList.rend());
190
191 BlackboxWindowList::iterator it = lst.begin();
192 const BlackboxWindowList::iterator end = lst.end();
193 for (; it != end; ++it) {
194 BlackboxWindow *bw = *it;
195 if (! bw->isStuck())
196 bw->withdraw();
197 }
198 }
199
200
201 void Workspace::removeAll(void) {
202 while (! windowList.empty())
203 windowList.front()->iconify();
204 }
205
206
207 /*
208 * returns the number of transients for win, plus the number of transients
209 * associated with each transient of win
210 */
211 static int countTransients(const BlackboxWindow * const win) {
212 int ret = win->getTransients().size();
213 if (ret > 0) {
214 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
215 for (it = win->getTransients().begin(); it != end; ++it) {
216 ret += countTransients(*it);
217 }
218 }
219 return ret;
220 }
221
222
223 /*
224 * puts the transients of win into the stack. windows are stacked above
225 * the window before it in the stackvector being iterated, meaning
226 * stack[0] is on bottom, stack[1] is above stack[0], stack[2] is above
227 * stack[1], etc...
228 */
229 void Workspace::raiseTransients(const BlackboxWindow * const win,
230 StackVector::iterator &stack) {
231 if (win->getTransients().size() == 0) return; // nothing to do
232
233 // put win's transients in the stack
234 BlackboxWindowList::const_iterator it, end = win->getTransients().end();
235 for (it = win->getTransients().begin(); it != end; ++it) {
236 *stack++ = (*it)->getFrameWindow();
237 screen->updateNetizenWindowRaise((*it)->getClientWindow());
238
239 if (! (*it)->isIconic()) {
240 Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
241 wkspc->stackingList.remove((*it));
242 wkspc->stackingList.push_front((*it));
243 }
244 }
245
246 // put transients of win's transients in the stack
247 for (it = win->getTransients().begin(); it != end; ++it) {
248 raiseTransients(*it, stack);
249 }
250 }
251
252
253 void Workspace::lowerTransients(const BlackboxWindow * const win,
254 StackVector::iterator &stack) {
255 if (win->getTransients().size() == 0) return; // nothing to do
256
257 // put transients of win's transients in the stack
258 BlackboxWindowList::const_reverse_iterator it,
259 end = win->getTransients().rend();
260 for (it = win->getTransients().rbegin(); it != end; ++it) {
261 lowerTransients(*it, stack);
262 }
263
264 // put win's transients in the stack
265 for (it = win->getTransients().rbegin(); it != end; ++it) {
266 *stack++ = (*it)->getFrameWindow();
267 screen->updateNetizenWindowLower((*it)->getClientWindow());
268
269 if (! (*it)->isIconic()) {
270 Workspace *wkspc = screen->getWorkspace((*it)->getWorkspaceNumber());
271 wkspc->stackingList.remove((*it));
272 wkspc->stackingList.push_back((*it));
273 }
274 }
275 }
276
277
278 void Workspace::raiseWindow(BlackboxWindow *w) {
279 BlackboxWindow *win = w;
280
281 // walk up the transient_for's to the window that is not a transient
282 while (win->isTransient()) {
283 if (! win->getTransientFor()) break;
284 win = win->getTransientFor();
285 }
286
287 // get the total window count (win and all transients)
288 unsigned int i = 1 + countTransients(win);
289
290 // stack the window with all transients above
291 StackVector stack_vector(i);
292 StackVector::iterator stack = stack_vector.begin();
293
294 *(stack++) = win->getFrameWindow();
295 screen->updateNetizenWindowRaise(win->getClientWindow());
296 if (! win->isIconic()) {
297 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
298 wkspc->stackingList.remove(win);
299 wkspc->stackingList.push_front(win);
300 }
301
302 raiseTransients(win, stack);
303
304 screen->raiseWindows(&stack_vector[0], stack_vector.size());
305 }
306
307
308 void Workspace::lowerWindow(BlackboxWindow *w) {
309 BlackboxWindow *win = w;
310
311 // walk up the transient_for's to the window that is not a transient
312 while (win->isTransient()) {
313 if (! win->getTransientFor()) break;
314 win = win->getTransientFor();
315 }
316
317 // get the total window count (win and all transients)
318 unsigned int i = 1 + countTransients(win);
319
320 // stack the window with all transients above
321 StackVector stack_vector(i);
322 StackVector::iterator stack = stack_vector.begin();
323
324 lowerTransients(win, stack);
325
326 *(stack++) = win->getFrameWindow();
327 screen->updateNetizenWindowLower(win->getClientWindow());
328 if (! win->isIconic()) {
329 Workspace *wkspc = screen->getWorkspace(win->getWorkspaceNumber());
330 wkspc->stackingList.remove(win);
331 wkspc->stackingList.push_back(win);
332 }
333
334 screen->lowerWindows(&stack_vector[0], stack_vector.size());
335 }
336
337
338 void Workspace::reconfigure(void) {
339 clientmenu->reconfigure();
340 std::for_each(windowList.begin(), windowList.end(),
341 std::mem_fun(&BlackboxWindow::reconfigure));
342 }
343
344
345 BlackboxWindow *Workspace::getWindow(unsigned int index) {
346 if (index < windowList.size()) {
347 BlackboxWindowList::iterator it = windowList.begin();
348 for(; index > 0; --index, ++it); /* increment to index */
349 return *it;
350 }
351 return 0;
352 }
353
354
355 BlackboxWindow*
356 Workspace::getNextWindowInList(BlackboxWindow *w) {
357 BlackboxWindowList::iterator it = std::find(windowList.begin(),
358 windowList.end(),
359 w);
360 assert(it != windowList.end()); // window must be in list
361 ++it; // next window
362 if (it == windowList.end())
363 return windowList.front(); // if we walked off the end, wrap around
364
365 return *it;
366 }
367
368
369 BlackboxWindow* Workspace::getPrevWindowInList(BlackboxWindow *w) {
370 BlackboxWindowList::iterator it = std::find(windowList.begin(),
371 windowList.end(),
372 w);
373 assert(it != windowList.end()); // window must be in list
374 if (it == windowList.begin())
375 return windowList.back(); // if we walked of the front, wrap around
376
377 return *(--it);
378 }
379
380
381 BlackboxWindow* Workspace::getTopWindowOnStack(void) const {
382 return stackingList.front();
383 }
384
385
386 void Workspace::sendWindowList(Netizen &n) {
387 BlackboxWindowList::iterator it = windowList.begin(),
388 end = windowList.end();
389 for(; it != end; ++it)
390 n.sendWindowAdd((*it)->getClientWindow(), getID());
391 }
392
393
394 unsigned int Workspace::getCount(void) const {
395 return windowList.size();
396 }
397
398
399 void Workspace::appendStackOrder(BlackboxWindowList &stack_order) const {
400 BlackboxWindowList::const_reverse_iterator it = stackingList.rbegin();
401 const BlackboxWindowList::const_reverse_iterator end = stackingList.rend();
402 for (; it != end; ++it)
403 stack_order.push_back(*it);
404 }
405
406
407 bool Workspace::isCurrent(void) const {
408 return (id == screen->getCurrentWorkspaceID());
409 }
410
411
412 bool Workspace::isLastWindow(const BlackboxWindow* const w) const {
413 return (w == windowList.back());
414 }
415
416
417 void Workspace::setCurrent(void) {
418 screen->changeWorkspaceID(id);
419 }
420
421
422 void Workspace::readName(void) {
423 XAtom::StringVect namesList;
424 unsigned long numnames = id + 1;
425
426 // attempt to get from the _NET_WM_DESKTOP_NAMES property
427 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
428 XAtom::utf8, numnames, namesList) &&
429 namesList.size() > id) {
430 name = namesList[id];
431
432 clientmenu->setLabel(name);
433 clientmenu->update();
434 } else {
435 /*
436 Use a default name. This doesn't actually change the class. That will
437 happen after the setName changes the root property, and that change
438 makes its way back to this function.
439 */
440 string tmp =i18n(WorkspaceSet, WorkspaceDefaultNameFormat,
441 "Workspace %d");
442 assert(tmp.length() < 32);
443 char default_name[32];
444 sprintf(default_name, tmp.c_str(), id + 1);
445
446 setName(default_name); // save this into the _NET_WM_DESKTOP_NAMES property
447 }
448 }
449
450
451 void Workspace::setName(const string& new_name) {
452 // set the _NET_WM_DESKTOP_NAMES property with the new name
453 XAtom::StringVect namesList;
454 unsigned long numnames = (unsigned) -1;
455 if (xatom->getValue(screen->getRootWindow(), XAtom::net_desktop_names,
456 XAtom::utf8, numnames, namesList) &&
457 namesList.size() > id)
458 namesList[id] = new_name;
459 else
460 namesList.push_back(new_name);
461
462 xatom->setValue(screen->getRootWindow(), XAtom::net_desktop_names,
463 XAtom::utf8, namesList);
464 }
465
466
467 /*
468 * Calculate free space available for window placement.
469 */
470 typedef std::vector<Rect> rectList;
471
472 static rectList calcSpace(const Rect &win, const rectList &spaces) {
473 Rect isect, extra;
474 rectList result;
475 rectList::const_iterator siter, end = spaces.end();
476 for (siter = spaces.begin(); siter != end; ++siter) {
477 const Rect &curr = *siter;
478
479 if(! win.intersects(curr)) {
480 result.push_back(curr);
481 continue;
482 }
483
484 /* Use an intersection of win and curr to determine the space around
485 * curr that we can use.
486 *
487 * NOTE: the spaces calculated can overlap.
488 */
489 isect = curr & win;
490
491 // left
492 extra.setCoords(curr.left(), curr.top(),
493 isect.left() - 1, curr.bottom());
494 if (extra.valid()) result.push_back(extra);
495
496 // top
497 extra.setCoords(curr.left(), curr.top(),
498 curr.right(), isect.top() - 1);
499 if (extra.valid()) result.push_back(extra);
500
501 // right
502 extra.setCoords(isect.right() + 1, curr.top(),
503 curr.right(), curr.bottom());
504 if (extra.valid()) result.push_back(extra);
505
506 // bottom
507 extra.setCoords(curr.left(), isect.bottom() + 1,
508 curr.right(), curr.bottom());
509 if (extra.valid()) result.push_back(extra);
510 }
511 return result;
512 }
513
514
515 static bool rowRLBT(const Rect &first, const Rect &second) {
516 if (first.bottom() == second.bottom())
517 return first.right() > second.right();
518 return first.bottom() > second.bottom();
519 }
520
521 static bool rowRLTB(const Rect &first, const Rect &second) {
522 if (first.y() == second.y())
523 return first.right() > second.right();
524 return first.y() < second.y();
525 }
526
527 static bool rowLRBT(const Rect &first, const Rect &second) {
528 if (first.bottom() == second.bottom())
529 return first.x() < second.x();
530 return first.bottom() > second.bottom();
531 }
532
533 static bool rowLRTB(const Rect &first, const Rect &second) {
534 if (first.y() == second.y())
535 return first.x() < second.x();
536 return first.y() < second.y();
537 }
538
539 static bool colLRTB(const Rect &first, const Rect &second) {
540 if (first.x() == second.x())
541 return first.y() < second.y();
542 return first.x() < second.x();
543 }
544
545 static bool colLRBT(const Rect &first, const Rect &second) {
546 if (first.x() == second.x())
547 return first.bottom() > second.bottom();
548 return first.x() < second.x();
549 }
550
551 static bool colRLTB(const Rect &first, const Rect &second) {
552 if (first.right() == second.right())
553 return first.y() < second.y();
554 return first.right() > second.right();
555 }
556
557 static bool colRLBT(const Rect &first, const Rect &second) {
558 if (first.right() == second.right())
559 return first.bottom() > second.bottom();
560 return first.right() > second.right();
561 }
562
563
564 bool Workspace::smartPlacement(Rect& win, const Rect& availableArea) {
565 rectList spaces;
566 spaces.push_back(availableArea); //initially the entire screen is free
567
568 //Find Free Spaces
569 BlackboxWindowList::const_iterator wit = windowList.begin(),
570 end = windowList.end();
571 Rect tmp;
572 for (; wit != end; ++wit) {
573 const BlackboxWindow* const curr = *wit;
574
575 if (curr->isShaded() && screen->getPlaceIgnoreShaded()) continue;
576 if (curr->isMaximizedFull() && screen->getPlaceIgnoreMaximized()) continue;
577
578 tmp.setRect(curr->frameRect().x(), curr->frameRect().y(),
579 curr->frameRect().width() + screen->getBorderWidth(),
580 curr->frameRect().height() + screen->getBorderWidth());
581
582 spaces = calcSpace(tmp, spaces);
583 }
584
585 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
586 if(screen->getRowPlacementDirection() == BScreen::LeftRight) {
587 if(screen->getColPlacementDirection() == BScreen::TopBottom)
588 std::sort(spaces.begin(), spaces.end(), rowLRTB);
589 else
590 std::sort(spaces.begin(), spaces.end(), rowLRBT);
591 } else {
592 if(screen->getColPlacementDirection() == BScreen::TopBottom)
593 std::sort(spaces.begin(), spaces.end(), rowRLTB);
594 else
595 std::sort(spaces.begin(), spaces.end(), rowRLBT);
596 }
597 } else {
598 if(screen->getColPlacementDirection() == BScreen::TopBottom) {
599 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
600 std::sort(spaces.begin(), spaces.end(), colLRTB);
601 else
602 std::sort(spaces.begin(), spaces.end(), colRLTB);
603 } else {
604 if(screen->getRowPlacementDirection() == BScreen::LeftRight)
605 std::sort(spaces.begin(), spaces.end(), colLRBT);
606 else
607 std::sort(spaces.begin(), spaces.end(), colRLBT);
608 }
609 }
610
611 rectList::const_iterator sit = spaces.begin(), spaces_end = spaces.end();
612 for(; sit != spaces_end; ++sit) {
613 if (sit->width() >= win.width() && sit->height() >= win.height())
614 break;
615 }
616
617 if (sit == spaces_end)
618 return False;
619
620 //set new position based on the empty space found
621 const Rect& where = *sit;
622 win.setX(where.x());
623 win.setY(where.y());
624
625 // adjust the location() based on left/right and top/bottom placement
626 if (screen->getPlacementPolicy() == BScreen::RowSmartPlacement) {
627 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
628 win.setX(where.right() - win.width());
629 if (screen->getColPlacementDirection() == BScreen::BottomTop)
630 win.setY(where.bottom() - win.height());
631 } else {
632 if (screen->getColPlacementDirection() == BScreen::BottomTop)
633 win.setY(win.y() + where.height() - win.height());
634 if (screen->getRowPlacementDirection() == BScreen::RightLeft)
635 win.setX(win.x() + where.width() - win.width());
636 }
637 return True;
638 }
639
640
641 bool Workspace::underMousePlacement(Rect &win, const Rect &availableArea) {
642 int x, y, rx, ry;
643 Window c, r;
644 unsigned int m;
645 XQueryPointer(screen->getBlackbox()->getXDisplay(), screen->getRootWindow(),
646 &r, &c, &rx, &ry, &x, &y, &m);
647 x = rx - win.width() / 2;
648 y = ry - win.height() / 2;
649
650 if (x < availableArea.x())
651 x = availableArea.x();
652 if (y < availableArea.y())
653 y = availableArea.y();
654 if (x + win.width() > availableArea.x() + availableArea.width())
655 x = availableArea.x() + availableArea.width() - win.width();
656 if (y + win.height() > availableArea.y() + availableArea.height())
657 y = availableArea.y() + availableArea.height() - win.height();
658
659 win.setX(x);
660 win.setY(y);
661
662 return True;
663 }
664
665
666 bool Workspace::cascadePlacement(Rect &win, const Rect &availableArea) {
667 if ((cascade_x > static_cast<signed>(availableArea.width() / 2)) ||
668 (cascade_y > static_cast<signed>(availableArea.height() / 2)))
669 cascade_x = cascade_y = 32;
670
671 if (cascade_x == 32) {
672 cascade_x += availableArea.x();
673 cascade_y += availableArea.y();
674 }
675
676 win.setPos(cascade_x, cascade_y);
677
678 return True;
679 }
680
681
682 void Workspace::placeWindow(BlackboxWindow *win) {
683 Rect availableArea(screen->availableArea()),
684 new_win(availableArea.x(), availableArea.y(),
685 win->frameRect().width(), win->frameRect().height());
686 bool placed = False;
687
688 switch (screen->getPlacementPolicy()) {
689 case BScreen::RowSmartPlacement:
690 case BScreen::ColSmartPlacement:
691 placed = smartPlacement(new_win, availableArea);
692 break;
693 case BScreen::UnderMousePlacement:
694 placed = underMousePlacement(new_win, availableArea);
695 default:
696 break; // handled below
697 } // switch
698
699 if (placed == False) {
700 cascadePlacement(new_win, availableArea);
701 cascade_x += win->getTitleHeight() + (screen->getBorderWidth() * 2);
702 cascade_y += win->getTitleHeight() + (screen->getBorderWidth() * 2);
703 }
704
705 if (new_win.right() > availableArea.right())
706 new_win.setX(availableArea.left());
707 if (new_win.bottom() > availableArea.bottom())
708 new_win.setY(availableArea.top());
709 win->configure(new_win.x(), new_win.y(), new_win.width(), new_win.height());
710 }
This page took 0.067313 seconds and 5 git commands to generate.