Forgot to init restack sibling to NULL
[mikachu/openbox.git] / openbox / stacking.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    stacking.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "openbox.h"
21 #include "screen.h"
22 #include "focus.h"
23 #include "client.h"
24 #include "group.h"
25 #include "frame.h"
26 #include "window.h"
27 #include "event.h"
28 #include "debug.h"
29 #include "dock.h"
30 #include "obt/prop.h"
31
32 GList  *stacking_list = NULL;
33 GList  *stacking_list_tail = NULL;
34 /*! When true, stacking changes will not be reflected on the screen.  This is
35   to freeze the on-screen stacking order while a window is being temporarily
36   raised during focus cycling */
37 static gboolean pause_changes = FALSE;
38
39 void stacking_set_list(void)
40 {
41     Window *windows = NULL;
42     GList *it;
43     guint i = 0;
44
45     /* on shutdown, don't update the properties, so that we can read it back
46        in on startup and re-stack the windows as they were before we shut down
47     */
48     if (ob_state() == OB_STATE_EXITING) return;
49
50     /* create an array of the window ids (from bottom to top,
51        reverse order!) */
52     if (stacking_list) {
53         windows = g_new(Window, g_list_length(stacking_list));
54         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
55             if (WINDOW_IS_CLIENT(it->data))
56                 windows[i++] = WINDOW_AS_CLIENT(it->data)->window;
57         }
58     }
59
60     OBT_PROP_SETA32(obt_root(ob_screen), NET_CLIENT_LIST_STACKING, WINDOW,
61                     (gulong*)windows, i);
62
63     g_free(windows);
64 }
65
66 static void do_restack(GList *wins, GList *before)
67 {
68     GList *it;
69     Window *win;
70     gint i;
71
72 #ifdef DEBUG
73     GList *next;
74
75     g_assert(wins);
76     /* pls only restack stuff in the same layer at a time */
77     for (it = wins; it; it = next) {
78         next = g_list_next(it);
79         if (!next) break;
80         g_assert (window_layer(it->data) == window_layer(next->data));
81     }
82     if (before)
83         g_assert(window_layer(it->data) >= window_layer(before->data));
84 #endif
85
86     win = g_new(Window, g_list_length(wins) + 1);
87
88     if (before == stacking_list)
89         win[0] = screen_support_win;
90     else if (!before)
91         win[0] = window_top(g_list_last(stacking_list)->data);
92     else
93         win[0] = window_top(g_list_previous(before)->data);
94
95     for (i = 1, it = wins; it; ++i, it = g_list_next(it)) {
96         win[i] = window_top(it->data);
97         g_assert(win[i] != None); /* better not call stacking shit before
98                                      setting your top level window value */
99         stacking_list = g_list_insert_before(stacking_list, before, it->data);
100     }
101
102 #ifdef DEBUG
103     /* some debug checking of the stacking list's order */
104     for (it = stacking_list; ; it = next) {
105         next = g_list_next(it);
106         if (!next) break;
107         g_assert(window_layer(it->data) >= window_layer(next->data));
108     }
109 #endif
110
111     if (!pause_changes)
112         XRestackWindows(obt_display, win, i);
113     g_free(win);
114
115     stacking_set_list();
116 }
117
118 void stacking_temp_raise(ObWindow *window)
119 {
120     Window win[2];
121     GList *it;
122     gulong start;
123
124     /* don't use this for internal windows..! it would lower them.. */
125     g_assert(window_layer(window) < OB_STACKING_LAYER_INTERNAL);
126
127     /* find the window to drop it underneath */
128     win[0] = screen_support_win;
129     for (it = stacking_list; it; it = g_list_next(it)) {
130         ObWindow *w = it->data;
131         if (window_layer(w) >= OB_STACKING_LAYER_INTERNAL)
132             win[0] = window_top(w);
133         else
134             break;
135     }
136
137     win[1] = window_top(window);
138     start = event_start_ignore_all_enters();
139     XRestackWindows(obt_display, win, 2);
140     event_end_ignore_all_enters(start);
141
142     pause_changes = TRUE;
143 }
144
145 void stacking_restore(void)
146 {
147     Window *win;
148     GList *it;
149     gint i;
150     gulong start;
151
152     win = g_new(Window, g_list_length(stacking_list) + 1);
153     win[0] = screen_support_win;
154     for (i = 1, it = stacking_list; it; ++i, it = g_list_next(it))
155         win[i] = window_top(it->data);
156     start = event_start_ignore_all_enters();
157     XRestackWindows(obt_display, win, i);
158     event_end_ignore_all_enters(start);
159     g_free(win);
160
161     pause_changes = FALSE;
162 }
163
164 static void do_raise(GList *wins)
165 {
166     GList *it;
167     GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
168     gint i;
169
170     for (it = wins; it; it = g_list_next(it)) {
171         ObStackingLayer l;
172
173         l = window_layer(it->data);
174         layer[l] = g_list_append(layer[l], it->data);
175     }
176
177     it = stacking_list;
178     for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
179         if (layer[i]) {
180             for (; it; it = g_list_next(it)) {
181                 /* look for the top of the layer */
182                 if (window_layer(it->data) <= (ObStackingLayer) i)
183                     break;
184             }
185             do_restack(layer[i], it);
186             g_list_free(layer[i]);
187         }
188     }
189 }
190
191 static void do_lower(GList *wins)
192 {
193     GList *it;
194     GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
195     gint i;
196
197     for (it = wins; it; it = g_list_next(it)) {
198         ObStackingLayer l;
199
200         l = window_layer(it->data);
201         layer[l] = g_list_append(layer[l], it->data);
202     }
203
204     it = stacking_list;
205     for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
206         if (layer[i]) {
207             for (; it; it = g_list_next(it)) {
208                 /* look for the top of the next layer down */
209                 if (window_layer(it->data) < (ObStackingLayer) i)
210                     break;
211             }
212             do_restack(layer[i], it);
213             g_list_free(layer[i]);
214         }
215     }
216 }
217
218 static void restack_windows(ObClient *selected, gboolean raise)
219 {
220     GList *it, *last, *below, *above, *next;
221     GList *wins = NULL;
222
223     GList *group_helpers = NULL;
224     GList *group_modals = NULL;
225     GList *group_trans = NULL;
226     GList *modals = NULL;
227     GList *trans = NULL;
228
229     if (raise) {
230         ObClient *p;
231
232         /* if a window is modal for another single window, then raise it to the
233            top too, the same is done with the focus order */
234         while (selected->modal && (p = client_direct_parent(selected)))
235             selected = p;
236     }
237
238     /* remove first so we can't run into ourself */
239     it = g_list_find(stacking_list, selected);
240     g_assert(it);
241     stacking_list = g_list_delete_link(stacking_list, it);
242
243     /* go from the bottom of the stacking list up. don't move any other windows
244        when lowering, we call this for each window independently */
245     if (raise) {
246         for (it = g_list_last(stacking_list); it; it = next) {
247             next = g_list_previous(it);
248
249             if (WINDOW_IS_CLIENT(it->data)) {
250                 ObClient *ch = it->data;
251
252                 /* only move windows in the same stacking layer */
253                 if (ch->layer == selected->layer &&
254                     /* looking for windows that are transients, and so would
255                        remain above the selected window */
256                     client_search_transient(selected, ch))
257                 {
258                     if (client_is_direct_child(selected, ch)) {
259                         if (ch->modal)
260                             modals = g_list_prepend(modals, ch);
261                         else
262                             trans = g_list_prepend(trans, ch);
263                     }
264                     else if (client_helper(ch)) {
265                         if (selected->transient) {
266                             /* helpers do not stay above transient windows */
267                             continue;
268                         }
269                         group_helpers = g_list_prepend(group_helpers, ch);
270                     }
271                     else {
272                         if (ch->modal)
273                             group_modals = g_list_prepend(group_modals, ch);
274                         else
275                             group_trans = g_list_prepend(group_trans, ch);
276                     }
277                     stacking_list = g_list_delete_link(stacking_list, it);
278                 }
279             }
280         }
281     }
282
283     /* put modals above other direct transients */
284     wins = g_list_concat(modals, trans);
285
286     /* put helpers below direct transients */
287     wins = g_list_concat(wins, group_helpers);
288
289     /* put the selected window right below these children */
290     wins = g_list_append(wins, selected);
291
292     /* if selected window is transient for group then raise it above others */
293     if (selected->transient_for_group) {
294         /* if it's modal, raise it above those also */
295         if (selected->modal) {
296             wins = g_list_concat(wins, group_modals);
297             group_modals = NULL;
298         }
299         wins = g_list_concat(wins, group_trans);
300         group_trans = NULL;
301     }
302
303     /* find where to put the selected window, start from bottom of list,
304        this is the window below everything we are re-adding to the list */
305     last = NULL;
306     for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
307     {
308         if (window_layer(it->data) < selected->layer) {
309             last = it;
310             continue;
311         }
312         /* if lowering, stop at the beginning of the layer */
313         if (!raise)
314             break;
315         /* if raising, stop at the end of the layer */
316         if (window_layer(it->data) > selected->layer)
317             break;
318
319         last = it;
320     }
321
322     /* save this position in the stacking list */
323     below = last;
324
325     /* find where to put the group transients, start from the top of list */
326     for (it = stacking_list; it; it = g_list_next(it)) {
327         /* skip past higher layers */
328         if (window_layer(it->data) > selected->layer)
329             continue;
330         /* if we reach the end of the layer (how?) then don't go further */
331         if (window_layer(it->data) < selected->layer)
332             break;
333         /* stop when we reach the first window in the group */
334         if (WINDOW_IS_CLIENT(it->data)) {
335             ObClient *c = it->data;
336             if (c->group == selected->group)
337                 break;
338         }
339         /* if we don't hit any other group members, stop here because this
340            is where we are putting the selected window (and its children) */
341         if (it == below)
342             break;
343     }
344
345     /* save this position, this is the top of the group of windows between the
346        group transient ones we're restacking and the others up above that we're
347        restacking
348
349        we actually want to save 1 position _above_ that, for for loops to work
350        nicely, so move back one position in the list while saving it
351     */
352     above = it ? g_list_previous(it) : g_list_last(stacking_list);
353
354     /* put the windows inside the gap to the other windows we're stacking
355        into the restacking list, go from the bottom up so that we can use
356        g_list_prepend */
357     if (below) it = g_list_previous(below);
358     else       it = g_list_last(stacking_list);
359     for (; it != above; it = next) {
360         next = g_list_previous(it);
361         wins = g_list_prepend(wins, it->data);
362         stacking_list = g_list_delete_link(stacking_list, it);
363     }
364
365     /* group transients go above the rest of the stuff acquired to now */
366     wins = g_list_concat(group_trans, wins);
367     /* group modals go on the very top */
368     wins = g_list_concat(group_modals, wins);
369
370     do_restack(wins, below);
371     g_list_free(wins);
372
373     /* lower our parents after us, so they go below us */
374     if (!raise && selected->parents) {
375         GSList *parents_copy, *sit;
376         GSList *reorder = NULL;
377
378         parents_copy = g_slist_copy(selected->parents);
379
380         /* go thru stacking list backwards so we can use g_slist_prepend */
381         for (it = g_list_last(stacking_list); it && parents_copy;
382              it = g_list_previous(it))
383             if ((sit = g_slist_find(parents_copy, it->data))) {
384                 reorder = g_slist_prepend(reorder, sit->data);
385                 parents_copy = g_slist_delete_link(parents_copy, sit);
386             }
387         g_assert(parents_copy == NULL);
388
389         /* call restack for each of these to lower them */
390         for (sit = reorder; sit; sit = g_slist_next(sit))
391             restack_windows(sit->data, raise);
392     }
393 }
394
395 void stacking_raise(ObWindow *window)
396 {
397     if (WINDOW_IS_CLIENT(window)) {
398         ObClient *selected;
399         selected = WINDOW_AS_CLIENT(window);
400         restack_windows(selected, TRUE);
401     } else {
402         GList *wins;
403         wins = g_list_append(NULL, window);
404         stacking_list = g_list_remove(stacking_list, window);
405         do_raise(wins);
406         g_list_free(wins);
407     }
408     stacking_list_tail = g_list_last(stacking_list);
409 }
410
411 void stacking_lower(ObWindow *window)
412 {
413     if (WINDOW_IS_CLIENT(window)) {
414         ObClient *selected;
415         selected = WINDOW_AS_CLIENT(window);
416         restack_windows(selected, FALSE);
417     } else {
418         GList *wins;
419         wins = g_list_append(NULL, window);
420         stacking_list = g_list_remove(stacking_list, window);
421         do_lower(wins);
422         g_list_free(wins);
423     }
424     stacking_list_tail = g_list_last(stacking_list);
425 }
426
427 void stacking_above(ObWindow *window, ObWindow *above)
428 {
429     GList *wins, *before;
430
431     if (window_layer(window) != window_layer(above))
432         return;
433
434     wins = g_list_append(NULL, window);
435     stacking_list = g_list_remove(stacking_list, window);
436     before = g_list_previous(g_list_find(stacking_list, above));
437     do_restack(wins, before);
438     g_list_free(wins);
439 }
440
441 void stacking_below(ObWindow *window, ObWindow *below)
442 {
443     GList *wins, *before;
444
445     if (window_layer(window) != window_layer(below))
446         return;
447
448     wins = g_list_append(NULL, window);
449     stacking_list = g_list_remove(stacking_list, window);
450     before = g_list_next(g_list_find(stacking_list, below));
451     do_restack(wins, before);
452     g_list_free(wins);
453     stacking_list_tail = g_list_last(stacking_list);
454 }
455
456 void stacking_add(ObWindow *win)
457 {
458     g_assert(screen_support_win != None); /* make sure I dont break this in the
459                                              future */
460     /* don't add windows that are being unmanaged ! */
461     if (WINDOW_IS_CLIENT(win)) g_assert(WINDOW_AS_CLIENT(win)->managed);
462
463     stacking_list = g_list_append(stacking_list, win);
464
465     stacking_raise(win);
466     /* stacking_list_tail set by stacking_raise() */
467 }
468
469 static GList *find_highest_relative(ObClient *client)
470 {
471     GList *ret = NULL;
472
473     if (client->parents) {
474         GList *it;
475         GSList *top;
476
477         /* get all top level relatives of this client */
478         top = client_search_all_top_parents_layer(client);
479
480         /* go from the top of the stacking order down */
481         for (it = stacking_list; !ret && it; it = g_list_next(it)) {
482             if (WINDOW_IS_CLIENT(it->data)) {
483                 ObClient *c = it->data;
484                 /* only look at windows in the same layer and that are
485                    visible */
486                 if (c->layer == client->layer &&
487                     !c->iconic &&
488                     (c->desktop == client->desktop ||
489                      c->desktop == DESKTOP_ALL ||
490                      client->desktop == DESKTOP_ALL))
491                 {
492                     GSList *sit;
493
494                     /* go through each top level parent and see it this window
495                        is related to them */
496                     for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
497                         ObClient *topc = sit->data;
498
499                         /* are they related ? */
500                         if (topc == c || client_search_transient(topc, c))
501                             ret = it;
502                     }
503                 }
504             }
505         }
506     }
507     return ret;
508 }
509
510 void stacking_add_nonintrusive(ObWindow *win)
511 {
512     ObClient *client;
513     GList *it_below = NULL; /* this client will be below us */
514     GList *it_above;
515     GList *wins;
516
517     if (!WINDOW_IS_CLIENT(win)) {
518         stacking_add(win); /* no special rules for others */
519         return;
520     }
521
522     client = WINDOW_AS_CLIENT(win);
523
524     /* don't add windows that are being unmanaged ! */
525     g_assert(client->managed);
526
527     /* insert above its highest parent (or its highest child !) */
528     it_below = find_highest_relative(client);
529
530     if (!it_below) {
531         /* nothing to put it directly above, so try find the focused client
532            to put it underneath it */
533         if (focus_client && client != focus_client &&
534             focus_client->layer == client->layer)
535         {
536             it_below = g_list_find(stacking_list, focus_client);
537             /* this can give NULL, but it means the focused window is on the
538                bottom of the stacking order, so go to the bottom in that case,
539                below it */
540             it_below = g_list_next(it_below);
541         }
542         else {
543             /* There is no window to put this directly above, so put it at the
544                top, so you know it is there.
545
546                It used to do this only if the window was focused and lower
547                it otherwise.
548
549                We also put it at the top not the bottom to fix a bug with
550                fullscreen windows. When focusLast is off and followsMouse is
551                on, when you switch desktops, the fullscreen window loses
552                focus and goes into its lower layer. If this puts it at the
553                bottom then when you come back to the desktop, the window is
554                at the bottom and won't get focus back.
555             */
556             it_below = stacking_list;
557         }
558     }
559
560     /* make sure it's not in the wrong layer though ! */
561     for (; it_below; it_below = g_list_next(it_below)) {
562         /* stop when the window is not in a higher layer than the window
563            it is going above (it_below) */
564         if (client->layer >= window_layer(it_below->data))
565             break;
566     }
567     for (; it_below != stacking_list; it_below = it_above) {
568         /* stop when the window is not in a lower layer than the
569            window it is going under (it_above) */
570         it_above = it_below ?
571             g_list_previous(it_below) : g_list_last(stacking_list);
572         if (client->layer <= window_layer(it_above->data))
573             break;
574     }
575
576     wins = g_list_append(NULL, win);
577     do_restack(wins, it_below);
578     g_list_free(wins);
579     stacking_list_tail = g_list_last(stacking_list);
580 }
581
582 ObWindow *stacking_occluded(ObClient *client, ObWindow *sibling_win)
583 {
584     GList *it;
585     ObWindow *occluded = NULL;
586     ObClient *sibling = NULL;
587
588     if (sibling_win && WINDOW_IS_CLIENT(sibling_win))
589         sibling = WINDOW_AS_CLIENT(sibling_win);
590
591     /* no need for any looping in this case */
592     if (sibling && client->layer != sibling->layer)
593         return NULL;
594
595     for (it = g_list_previous(g_list_find(stacking_list, client)); it;
596          it = g_list_previous(it))
597         if (WINDOW_IS_CLIENT(it->data)) {
598             ObClient *c = it->data;
599             if (!c->iconic &&
600                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
601                  c->desktop == client->desktop) &&
602                 !client_search_transient(client, c))
603             {
604                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
605                 {
606                     if (sibling != NULL) {
607                         if (c == sibling) {
608                             occluded = sibling_win;
609                             break;
610                         }
611                     }
612                     else if (c->layer == client->layer) {
613                         occluded = CLIENT_AS_WINDOW(c);
614                         break;
615                     }
616                     else if (c->layer > client->layer)
617                         break; /* we past its layer */
618                 }
619             }
620         }
621         else if (WINDOW_IS_DOCK(it->data)) {
622             ObDock *dock = it->data;
623             if (RECT_INTERSECTS_RECT(dock->area, client->frame->area))
624             {
625                 if (sibling_win != NULL) {
626                     if (DOCK_AS_WINDOW(dock) == sibling_win) {
627                         occluded = sibling_win;
628                         break;
629                     }
630                 }
631                 else
632                     occluded = DOCK_AS_WINDOW(dock);
633             }
634         }
635     return occluded;
636 }
637
638 ObWindow *stacking_occludes(ObClient *client, ObWindow *sibling_win)
639 {
640     GList *it;
641     ObWindow *occludes = NULL;
642     ObClient *sibling = NULL;
643
644     if (sibling_win && WINDOW_IS_CLIENT(sibling_win))
645         sibling = WINDOW_AS_CLIENT(sibling_win);
646
647     /* no need for any looping in this case */
648     if (sibling && client->layer != sibling->layer)
649         return NULL;
650
651     for (it = g_list_next(g_list_find(stacking_list, client));
652          it; it = g_list_next(it))
653         if (WINDOW_IS_CLIENT(it->data)) {
654             ObClient *c = it->data;
655             if (!c->iconic &&
656                 (c->desktop == DESKTOP_ALL || client->desktop == DESKTOP_ALL ||
657                  c->desktop == client->desktop) &&
658                 !client_search_transient(c, client))
659             {
660                 if (RECT_INTERSECTS_RECT(c->frame->area, client->frame->area))
661                 {
662                     if (sibling != NULL) {
663                         if (c == sibling) {
664                             occludes = sibling_win;
665                             break;
666                         }
667                     }
668                     else if (c->layer == client->layer) {
669                         occludes = CLIENT_AS_WINDOW(c);
670                         break;
671                     }
672                     else if (c->layer < client->layer)
673                         break; /* we past its layer */
674                 }
675             }
676         }
677         else if (WINDOW_IS_DOCK(it->data)) {
678             ObDock *dock = it->data;
679             if (RECT_INTERSECTS_RECT(dock->area, client->frame->area))
680             {
681                 if (sibling_win != NULL) {
682                     if (DOCK_AS_WINDOW(dock) == sibling_win) {
683                         occludes = sibling_win;
684                         break;
685                     }
686                 }
687                 else
688                     occludes = DOCK_AS_WINDOW(dock);
689             }
690         }
691     return occludes;
692 }
693
694 gboolean stacking_restack_request(ObClient *client, ObWindow *sibling_win,
695                                   gint detail)
696 {
697     gboolean ret = FALSE;
698
699     ObClient *sibling = NULL;
700
701     if (sibling_win && WINDOW_IS_CLIENT(sibling_win))
702         sibling = WINDOW_AS_CLIENT(sibling_win);
703
704     if (sibling && ((client->desktop != sibling->desktop &&
705                      client->desktop != DESKTOP_ALL &&
706                      sibling->desktop != DESKTOP_ALL) ||
707                     sibling->iconic))
708     {
709         ob_debug("Setting restack sibling to NULL, they are not on the same "
710                  "desktop or it is iconified");
711         sibling = NULL;
712     }
713
714     switch (detail) {
715     case Below:
716         ob_debug("Restack request Below for client %s sibling %s",
717                  client->title, sibling ? sibling->title : "(all)");
718         /* just lower it */
719         stacking_lower(CLIENT_AS_WINDOW(client));
720         ret = TRUE;
721         break;
722     case BottomIf:
723         ob_debug("Restack request BottomIf for client %s sibling %s",
724                  client->title, sibling ? sibling->title : "(all)");
725         /* if this client occludes sibling (or anything if NULL), then
726            lower it to the bottom */
727         if (stacking_occludes(client, sibling_win)) {
728             stacking_lower(CLIENT_AS_WINDOW(client));
729             ret = TRUE;
730         }
731         break;
732     case Above:
733         ob_debug("Restack request Above for client %s sibling %s",
734                  client->title, sibling ? sibling->title : "(all)");
735         stacking_raise(CLIENT_AS_WINDOW(client));
736         ret = TRUE;
737         break;
738     case TopIf:
739         ob_debug("Restack request TopIf for client %s sibling %s",
740                  client->title, sibling ? sibling->title : "(all)");
741         if (stacking_occluded(client, sibling_win)) {
742             stacking_raise(CLIENT_AS_WINDOW(client));
743             ret = TRUE;
744         }
745         break;
746     case Opposite:
747         ob_debug("Restack request Opposite for client %s sibling %s",
748                  client->title, sibling ? sibling->title : "(all)");
749         if (stacking_occluded(client, sibling_win)) {
750             stacking_raise(CLIENT_AS_WINDOW(client));
751             ret = TRUE;
752         }
753         else if (stacking_occludes(client, sibling_win)) {
754             stacking_lower(CLIENT_AS_WINDOW(client));
755             ret = TRUE;
756         }
757         break;
758     }
759     return ret;
760 }