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