]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
node processing code/macros, and other fixes
[mikachu/openbox.git] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "xerror.h"
4 #include "prop.h"
5 #include "config.h"
6 #include "screen.h"
7 #include "frame.h"
8 #include "framerender.h"
9 #include "focus.h"
10 #include "stacking.h"
11 #include "extensions.h"
12 #include "timer.h"
13 #include "dispatch.h"
14
15 #include <X11/Xlib.h>
16 #include <X11/keysym.h>
17 #include <X11/Xatom.h>
18 #ifdef HAVE_SYS_SELECT_H
19 #  include <sys/select.h>
20 #endif
21
22 static void event_process(XEvent *e);
23 static void event_handle_root(XEvent *e);
24 static void event_handle_client(Client *c, XEvent *e);
25
26 Time event_lasttime = 0;
27
28 /*! The value of the mask for the NumLock modifier */
29 unsigned int NumLockMask;
30 /*! The value of the mask for the ScrollLock modifier */
31 unsigned int ScrollLockMask;
32 /*! The key codes for the modifier keys */
33 static XModifierKeymap *modmap;
34 /*! Table of the constant modifier masks */
35 static const int mask_table[] = {
36     ShiftMask, LockMask, ControlMask, Mod1Mask,
37     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
38 };
39 static int mask_table_size;
40
41 void event_startup()
42 {
43     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
44      
45     /* get lock masks that are defined by the display (not constant) */
46     modmap = XGetModifierMapping(ob_display);
47     g_assert(modmap);
48     if (modmap && modmap->max_keypermod > 0) {
49         size_t cnt;
50         const size_t size = mask_table_size * modmap->max_keypermod;
51         /* get the values of the keyboard lock modifiers
52            Note: Caps lock is not retrieved the same way as Scroll and Num
53            lock since it doesn't need to be. */
54         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
55         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
56                                                      XK_Scroll_Lock);
57           
58         for (cnt = 0; cnt < size; ++cnt) {
59             if (! modmap->modifiermap[cnt]) continue;
60                
61             if (num_lock == modmap->modifiermap[cnt])
62                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
63             if (scroll_lock == modmap->modifiermap[cnt])
64                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
65         }
66     }
67 }
68
69 void event_shutdown()
70 {
71     XFreeModifiermap(modmap);
72 }
73
74 void event_loop()
75 {
76     fd_set selset;
77     XEvent e;
78     int x_fd;
79     struct timeval *wait;
80
81     while (TRUE) {
82         /*
83           There are slightly different event retrieval semantics here for
84           local (or high bandwidth) versus remote (or low bandwidth)
85           connections to the display/Xserver.
86         */
87         if (ob_remote) {
88             if (!XPending(ob_display))
89                 break;
90         } else {
91             /*
92               This XSync allows for far more compression of events, which
93               makes things like Motion events perform far far better. Since
94               it also means network traffic for every event instead of every
95               X events (where X is the number retrieved at a time), it
96               probably should not be used for setups where Openbox is
97               running on a remote/low bandwidth display/Xserver.
98             */
99             XSync(ob_display, FALSE);
100             if (!XEventsQueued(ob_display, QueuedAlready))
101                 break;
102         }
103         XNextEvent(ob_display, &e);
104
105         event_process(&e);
106     }
107      
108     timer_dispatch((GTimeVal**)&wait);
109     x_fd = ConnectionNumber(ob_display);
110     FD_ZERO(&selset);
111     FD_SET(x_fd, &selset);
112     select(x_fd + 1, &selset, NULL, NULL, wait);
113 }
114
115 static Window event_get_window(XEvent *e)
116 {
117     Window window;
118
119     /* pick a window */
120     switch (e->type) {
121     case MapRequest:
122         window = e->xmap.window;
123         break;
124     case UnmapNotify:
125         window = e->xunmap.window;
126         break;
127     case DestroyNotify:
128         window = e->xdestroywindow.window;
129         break;
130     case ConfigureRequest:
131         window = e->xconfigurerequest.window;
132         break;
133     default:
134 #ifdef XKB
135         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
136             switch (((XkbAnyEvent*)&e)->xkb_type) {
137             case XkbBellNotify:
138                 window = ((XkbBellNotifyEvent*)&e)->window;
139             default:
140                 window = None;
141             }
142         } else
143 #endif
144             window = e->xany.window;
145     }
146     return window;
147 }
148
149 static void event_set_lasttime(XEvent *e)
150 {
151     /* grab the lasttime and hack up the state */
152     switch (e->type) {
153     case ButtonPress:
154     case ButtonRelease:
155         event_lasttime = e->xbutton.time;
156         break;
157     case KeyPress:
158         event_lasttime = e->xkey.time;
159         break;
160     case KeyRelease:
161         event_lasttime = e->xkey.time;
162         break;
163     case MotionNotify:
164         event_lasttime = e->xmotion.time;
165         break;
166     case PropertyNotify:
167         event_lasttime = e->xproperty.time;
168         break;
169     case EnterNotify:
170     case LeaveNotify:
171         event_lasttime = e->xcrossing.time;
172         break;
173     default:
174         event_lasttime = CurrentTime;
175         break;
176     }
177 }
178
179 #define STRIP_MODS(s) \
180         s &= ~(LockMask | NumLockMask | ScrollLockMask), \
181         /* kill off the Button1Mask etc, only want the modifiers */ \
182         s &= (ControlMask | ShiftMask | Mod1Mask | \
183               Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) \
184
185 static void event_hack_mods(XEvent *e)
186 {
187     KeyCode *kp;
188     int i, k;
189
190     switch (e->type) {
191     case ButtonPress:
192     case ButtonRelease:
193         STRIP_MODS(e->xbutton.state);
194         break;
195     case KeyPress:
196         STRIP_MODS(e->xkey.state);
197         break;
198     case KeyRelease:
199         STRIP_MODS(e->xkey.state);
200         /* remove from the state the mask of the modifier being released, if
201            it is a modifier key being released (this is a little ugly..) */
202         kp = modmap->modifiermap;
203         for (i = 0; i < mask_table_size; ++i) {
204             for (k = 0; k < modmap->max_keypermod; ++k) {
205                 if (*kp == e->xkey.keycode) { /* found the keycode */
206                     /* remove the mask for it */
207                     e->xkey.state &= ~mask_table[i];
208                     /* cause the first loop to break; */
209                     i = mask_table_size;
210                     break; /* get outta here! */
211                 }
212                 ++kp;
213             }
214         }
215         break;
216     case MotionNotify:
217         STRIP_MODS(e->xmotion.state);
218         /* compress events */
219         {
220             XEvent ce;
221             while (XCheckTypedWindowEvent(ob_display, e->xmotion.window,
222                                           e->type, &ce)) {
223                 e->xmotion.x_root = ce.xmotion.x_root;
224                 e->xmotion.y_root = ce.xmotion.y_root;
225             }
226         }
227         break;
228     }
229 }
230
231 static gboolean event_ignore(XEvent *e, Client *client)
232 {
233     switch(e->type) {
234     case FocusIn:
235 #ifdef DEBUG_FOCUS
236         g_message("FocusIn on %lx mode %d detail %d", window,
237                   e->xfocus.mode, e->xfocus.detail);
238 #endif
239         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
240            because of RevertToPointerRoot. If the focus ends up reverting to
241            pointer root on a workspace change, then the FocusIn event that we
242            want will be of type NotifyAncestor. This situation does not occur
243            for FocusOut, so it is safely ignored there.
244         */
245         if (e->xfocus.detail == NotifyInferior ||
246             e->xfocus.detail > NotifyNonlinearVirtual ||
247             client == NULL) {
248             /* says a client was not found for the event (or a valid FocusIn
249                event was not found.
250             */
251             e->xfocus.window = None;
252             return TRUE;
253         }
254
255 #ifdef DEBUG_FOCUS
256         g_message("FocusIn on %lx", window);
257 #endif
258         break;
259     case FocusOut:
260 #ifdef DEBUG_FOCUS
261         g_message("FocusOut on %lx mode %d detail %d", window,
262                   e->xfocus.mode, e->xfocus.detail);
263 #endif
264         if (e->xfocus.mode == NotifyGrab ||
265             e->xfocus.detail == NotifyInferior ||
266             e->xfocus.detail == NotifyAncestor ||
267             e->xfocus.detail > NotifyNonlinearVirtual) return TRUE;
268  
269 #ifdef DEBUG_FOCUS
270        g_message("FocusOut on %lx", window);
271 #endif
272         /* Try process a FocusIn first, and if a legit one isn't found, then
273            do the fallback shiznit. */
274         {
275             XEvent fi, fo;
276             gboolean isfo = FALSE;
277
278             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
279                 event_process(&fi);
280
281                 /* when we have gotten a fi/fo pair, then see if there are any
282                    more fo's coming. if there are, then don't fallback just yet
283                 */
284                 if ((isfo = XCheckTypedEvent(ob_display, FocusOut, &fo)))
285                     XPutBackEvent(ob_display, &fo);
286
287                 /* secret magic way of event_process telling us that no client
288                    was found for the FocusIn event. ^_^ */
289                 if (!isfo && fi.xfocus.window == None)
290                     focus_fallback(Fallback_NoFocus);
291                 if (fi.xfocus.window == e->xfocus.window)
292                     return TRUE;
293             } else
294                 focus_fallback(Fallback_NoFocus);
295         }
296         break;
297     case EnterNotify:
298     case LeaveNotify:
299         /* NotifyUngrab occurs when a mouse button is released and the event is
300            caused, like when lowering a window */
301         if (e->xcrossing.mode == NotifyGrab ||
302             e->xcrossing.detail == NotifyInferior)
303             return TRUE;
304         break;
305     }
306     return FALSE;
307 }
308
309 static void event_process(XEvent *e)
310 {
311     Window window;
312     Client *client;
313
314     window = event_get_window(e);
315     client = g_hash_table_lookup(client_map, &window);
316     event_set_lasttime(e);
317     event_hack_mods(e);
318     if (event_ignore(e, client))
319         return;
320
321     /* deal with it in the kernel */
322     if (client)
323         event_handle_client(client, e);
324     else if (window == ob_root)
325         event_handle_root(e);
326     else if (e->type == MapRequest)
327         client_manage(window);
328     else if (e->type == ConfigureRequest) {
329         /* unhandled configure requests must be used to configure the
330            window directly */
331         XWindowChanges xwc;
332                
333         xwc.x = e->xconfigurerequest.x;
334         xwc.y = e->xconfigurerequest.y;
335         xwc.width = e->xconfigurerequest.width;
336         xwc.height = e->xconfigurerequest.height;
337         xwc.border_width = e->xconfigurerequest.border_width;
338         xwc.sibling = e->xconfigurerequest.above;
339         xwc.stack_mode = e->xconfigurerequest.detail;
340        
341         /* we are not to be held responsible if someone sends us an
342            invalid request! */
343         xerror_set_ignore(TRUE);
344         XConfigureWindow(ob_display, window,
345                          e->xconfigurerequest.value_mask, &xwc);
346         xerror_set_ignore(FALSE);
347     }
348
349     /* user input (action-bound) events */
350     /*
351     if (e->type == ButtonPress || e->type == ButtonRelease ||
352         e->type == MotionNotify)
353         mouse_event(e, client);
354     else if (e->type == KeyPress || e->type == KeyRelease)
355         ;
356     */
357
358     /* dispatch the event to registered handlers */
359     dispatch_x(e, client);
360 }
361
362 static void event_handle_root(XEvent *e)
363 {
364     Atom msgtype;
365      
366     switch(e->type) {
367     case ClientMessage:
368         if (e->xclient.format != 32) break;
369
370         msgtype = e->xclient.message_type;
371         if (msgtype == prop_atoms.net_current_desktop) {
372             unsigned int d = e->xclient.data.l[0];
373             if (d < screen_num_desktops)
374                 screen_set_desktop(d);
375         } else if (msgtype == prop_atoms.net_number_of_desktops) {
376             unsigned int d = e->xclient.data.l[0];
377             if (d > 0)
378                 screen_set_num_desktops(d);
379         } else if (msgtype == prop_atoms.net_showing_desktop) {
380             screen_show_desktop(e->xclient.data.l[0] != 0);
381         }
382         break;
383     case PropertyNotify:
384         if (e->xproperty.atom == prop_atoms.net_desktop_names)
385             screen_update_desktop_names();
386         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
387             screen_update_layout();
388         break;
389     }
390 }
391
392 static void event_handle_client(Client *client, XEvent *e)
393 {
394     XEvent ce;
395     Atom msgtype;
396     int i=0;
397      
398     switch (e->type) {
399     case ButtonPress:
400     case ButtonRelease:
401         switch (frame_context(client, e->xbutton.window)) {
402         case Context_Maximize:
403             client->frame->max_press = (e->type == ButtonPress);
404             framerender_frame(client->frame);
405             break;
406         case Context_Close:
407             client->frame->close_press = (e->type == ButtonPress);
408             framerender_frame(client->frame);
409             break;
410         case Context_Iconify:
411             client->frame->iconify_press = (e->type == ButtonPress);
412             framerender_frame(client->frame);
413             break;
414         case Context_AllDesktops:
415             client->frame->desk_press = (e->type == ButtonPress);
416             framerender_frame(client->frame);
417             break; 
418         case Context_Shade:
419             client->frame->shade_press = (e->type == ButtonPress);
420             framerender_frame(client->frame);
421             break;
422         default:
423             /* nothing changes with clicks for any other contexts */
424             break;
425         }
426         break;
427     case FocusIn:
428         focus_set_client(client);
429     case FocusOut:
430 #ifdef DEBUG_FOCUS
431         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
432                   client->window);
433 #endif
434         /* focus state can affect the stacking layer */
435         client_calc_layer(client);
436         frame_adjust_focus(client->frame);
437         break;
438     case EnterNotify:
439         if (client_normal(client)) {
440             if (ob_state == State_Starting) {
441                 /* move it to the top of the focus order */
442                 guint desktop = client->desktop;
443                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
444                 focus_order[desktop] = g_list_remove(focus_order[desktop],
445                                                      client);
446                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
447                                                       client);
448             } else if (config_focus_follow) {
449 #ifdef DEBUG_FOCUS
450                 g_message("EnterNotify on %lx, focusing window",
451                           client->window);
452 #endif
453                 client_focus(client);
454             }
455         }
456         break;
457     case ConfigureRequest:
458         /* compress these */
459         while (XCheckTypedWindowEvent(ob_display, client->window,
460                                       ConfigureRequest, &ce)) {
461             ++i;
462             /* XXX if this causes bad things.. we can compress config req's
463                with the same mask. */
464             e->xconfigurerequest.value_mask |=
465                 ce.xconfigurerequest.value_mask;
466             if (ce.xconfigurerequest.value_mask & CWX)
467                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
468             if (ce.xconfigurerequest.value_mask & CWY)
469                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
470             if (ce.xconfigurerequest.value_mask & CWWidth)
471                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
472             if (ce.xconfigurerequest.value_mask & CWHeight)
473                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
474             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
475                 e->xconfigurerequest.border_width =
476                     ce.xconfigurerequest.border_width;
477             if (ce.xconfigurerequest.value_mask & CWStackMode)
478                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
479         }
480
481         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
482         if (client->iconic || client->shaded) return;
483
484         if (e->xconfigurerequest.value_mask & CWBorderWidth)
485             client->border_width = e->xconfigurerequest.border_width;
486
487         /* resize, then move, as specified in the EWMH section 7.7 */
488         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
489                                                CWX | CWY)) {
490             int x, y, w, h;
491             Corner corner;
492                
493             x = (e->xconfigurerequest.value_mask & CWX) ?
494                 e->xconfigurerequest.x : client->area.x;
495             y = (e->xconfigurerequest.value_mask & CWY) ?
496                 e->xconfigurerequest.y : client->area.y;
497             w = (e->xconfigurerequest.value_mask & CWWidth) ?
498                 e->xconfigurerequest.width : client->area.width;
499             h = (e->xconfigurerequest.value_mask & CWHeight) ?
500                 e->xconfigurerequest.height : client->area.height;
501                
502             switch (client->gravity) {
503             case NorthEastGravity:
504             case EastGravity:
505                 corner = Corner_TopRight;
506                 break;
507             case SouthWestGravity:
508             case SouthGravity:
509                 corner = Corner_BottomLeft;
510                 break;
511             case SouthEastGravity:
512                 corner = Corner_BottomRight;
513                 break;
514             default:     /* NorthWest, Static, etc */
515                 corner = Corner_TopLeft;
516             }
517
518             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
519         }
520
521         if (e->xconfigurerequest.value_mask & CWStackMode) {
522             switch (e->xconfigurerequest.detail) {
523             case Below:
524             case BottomIf:
525                 stacking_lower(client);
526                 break;
527
528             case Above:
529             case TopIf:
530             default:
531                 stacking_raise(client);
532                 break;
533             }
534         }
535         break;
536     case UnmapNotify:
537         if (client->ignore_unmaps) {
538             client->ignore_unmaps--;
539             break;
540         }
541         client_unmanage(client);
542         break;
543     case DestroyNotify:
544         client_unmanage(client);
545         break;
546     case ReparentNotify:
547         /* this is when the client is first taken captive in the frame */
548         if (e->xreparent.parent == client->frame->plate) break;
549
550         /*
551           This event is quite rare and is usually handled in unmapHandler.
552           However, if the window is unmapped when the reparent event occurs,
553           the window manager never sees it because an unmap event is not sent
554           to an already unmapped window.
555         */
556
557         /* we don't want the reparent event, put it back on the stack for the
558            X server to deal with after we unmanage the window */
559         XPutBackEvent(ob_display, e);
560      
561         client_unmanage(client);
562         break;
563     case MapRequest:
564         g_message("MapRequest for 0x%lx", client->window);
565         if (!client->iconic) break; /* this normally doesn't happen, but if it
566                                        does, we don't want it! */
567         if (screen_showing_desktop)
568             screen_show_desktop(FALSE);
569         client_iconify(client, FALSE, TRUE);
570         if (!client->frame->visible)
571             /* if its not visible still, then don't mess with it */
572             break;
573         if (client->shaded)
574             client_shade(client, FALSE);
575         client_focus(client);
576         stacking_raise(client);
577         break;
578     case ClientMessage:
579         /* validate cuz we query stuff off the client here */
580         if (!client_validate(client)) break;
581   
582         if (e->xclient.format != 32) return;
583
584         msgtype = e->xclient.message_type;
585         if (msgtype == prop_atoms.wm_change_state) {
586             /* compress changes into a single change */
587             while (XCheckTypedWindowEvent(ob_display, e->type,
588                                           client->window, &ce)) {
589                 /* XXX: it would be nice to compress ALL messages of a
590                    type, not just messages in a row without other
591                    message types between. */
592                 if (ce.xclient.message_type != msgtype) {
593                     XPutBackEvent(ob_display, &ce);
594                     break;
595                 }
596                 e->xclient = ce.xclient;
597             }
598             client_set_wm_state(client, e->xclient.data.l[0]);
599         } else if (msgtype == prop_atoms.net_wm_desktop) {
600             /* compress changes into a single change */
601             while (XCheckTypedWindowEvent(ob_display, e->type,
602                                           client->window, &ce)) {
603                 /* XXX: it would be nice to compress ALL messages of a
604                    type, not just messages in a row without other
605                    message types between. */
606                 if (ce.xclient.message_type != msgtype) {
607                     XPutBackEvent(ob_display, &ce);
608                     break;
609                 }
610                 e->xclient = ce.xclient;
611             }
612             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
613                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
614                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
615                                    FALSE);
616         } else if (msgtype == prop_atoms.net_wm_state) {
617             /* can't compress these */
618             g_message("net_wm_state %s %ld %ld for 0x%lx",
619                       (e->xclient.data.l[0] == 0 ? "Remove" :
620                        e->xclient.data.l[0] == 1 ? "Add" :
621                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
622                       e->xclient.data.l[1], e->xclient.data.l[2],
623                       client->window);
624             client_set_state(client, e->xclient.data.l[0],
625                              e->xclient.data.l[1], e->xclient.data.l[2]);
626         } else if (msgtype == prop_atoms.net_close_window) {
627             g_message("net_close_window for 0x%lx", client->window);
628             client_close(client);
629         } else if (msgtype == prop_atoms.net_active_window) {
630             g_message("net_active_window for 0x%lx", client->window);
631             if (screen_showing_desktop)
632                 screen_show_desktop(FALSE);
633             if (client->iconic)
634                 client_iconify(client, FALSE, TRUE);
635             else if (!client->frame->visible)
636                 /* if its not visible for other reasons, then don't mess
637                    with it */
638                 break;
639             if (client->shaded)
640                 client_shade(client, FALSE);
641             client_focus(client);
642             stacking_raise(client);
643         }
644         break;
645     case PropertyNotify:
646         /* validate cuz we query stuff off the client here */
647         if (!client_validate(client)) break;
648   
649         /* compress changes to a single property into a single change */
650         while (XCheckTypedWindowEvent(ob_display, e->type,
651                                       client->window, &ce)) {
652             /* XXX: it would be nice to compress ALL changes to a property,
653                not just changes in a row without other props between. */
654             if (ce.xproperty.atom != e->xproperty.atom) {
655                 XPutBackEvent(ob_display, &ce);
656                 break;
657             }
658         }
659
660         msgtype = e->xproperty.atom;
661         if (msgtype == XA_WM_NORMAL_HINTS) {
662             client_update_normal_hints(client);
663             /* normal hints can make a window non-resizable */
664             client_setup_decor_and_functions(client);
665         }
666         else if (msgtype == XA_WM_HINTS)
667             client_update_wmhints(client);
668         else if (msgtype == XA_WM_TRANSIENT_FOR) {
669             client_update_transient_for(client);
670             client_get_type(client);
671             /* type may have changed, so update the layer */
672             client_calc_layer(client);
673             client_setup_decor_and_functions(client);
674         }
675         else if (msgtype == prop_atoms.net_wm_name ||
676                  msgtype == prop_atoms.wm_name)
677             client_update_title(client);
678         else if (msgtype == prop_atoms.net_wm_icon_name ||
679                  msgtype == prop_atoms.wm_icon_name)
680             client_update_icon_title(client);
681         else if (msgtype == prop_atoms.wm_class)
682             client_update_class(client);
683         else if (msgtype == prop_atoms.wm_protocols) {
684             client_update_protocols(client);
685             client_setup_decor_and_functions(client);
686         }
687         else if (msgtype == prop_atoms.net_wm_strut)
688             client_update_strut(client);
689         else if (msgtype == prop_atoms.net_wm_icon)
690             client_update_icons(client);
691         else if (msgtype == prop_atoms.kwm_win_icon)
692             client_update_kwm_icon(client);
693     default:
694         ;
695 #ifdef SHAPE
696         if (extensions_shape && e->type == extensions_shape_event_basep) {
697             client->shaped = ((XShapeEvent*)e)->shaped;
698             frame_adjust_shape(client->frame);
699         }
700 #endif
701     }
702 }