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