]> git.decadent.org.uk Git - ion3.git/blob - ioncore/clientwin.c
[svn-upgrade] Integrating new upstream version, ion3 (20070506)
[ion3.git] / ioncore / clientwin.c
1 /*
2  * ion/ioncore/clientwin.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * See the included file LICENSE for details.
7  */
8
9 #include <string.h>
10 #include <limits.h>
11 #include <ctype.h>
12
13 #include <libtu/objp.h>
14 #include <libtu/minmax.h>
15 #include <libextl/extl.h>
16 #include <libmainloop/defer.h>
17 #include <libmainloop/hooks.h>
18 #include "common.h"
19 #include "global.h"
20 #include "property.h"
21 #include "focus.h"
22 #include "sizehint.h"
23 #include "event.h"
24 #include "clientwin.h"
25 #include "colormap.h"
26 #include "resize.h"
27 #include "attach.h"
28 #include "regbind.h"
29 #include "bindmaps.h"
30 #include "names.h"
31 #include "saveload.h"
32 #include "manage.h"
33 #include "extlconv.h"
34 #include "fullscreen.h"
35 #include "event.h"
36 #include "rootwin.h"
37 #include "activity.h"
38 #include "netwm.h"
39 #include "xwindow.h"
40 #include "bindmaps.h"
41 #include "return.h"
42 #include "conf.h"
43 #include "group.h"
44
45
46 static void set_clientwin_state(WClientWin *cwin, int state);
47 static bool send_clientmsg(Window win, Atom a, Time stmp);
48
49
50 WHook *clientwin_do_manage_alt=NULL;
51 WHook *clientwin_mapped_hook=NULL;
52 WHook *clientwin_unmapped_hook=NULL;
53 WHook *clientwin_property_change_hook=NULL;
54
55
56 /*{{{ Get properties */
57
58
59 void clientwin_get_protocols(WClientWin *cwin)
60 {
61     Atom *protocols=NULL, *p;
62     int n;
63     
64     cwin->flags&=~(CLIENTWIN_P_WM_DELETE|CLIENTWIN_P_WM_TAKE_FOCUS);
65     
66     if(!XGetWMProtocols(ioncore_g.dpy, cwin->win, &protocols, &n))
67         return;
68     
69     for(p=protocols; n; n--, p++){
70         if(*p==ioncore_g.atom_wm_delete)
71             cwin->flags|=CLIENTWIN_P_WM_DELETE;
72         else if(*p==ioncore_g.atom_wm_take_focus)
73             cwin->flags|=CLIENTWIN_P_WM_TAKE_FOCUS;
74     }
75     
76     if(protocols!=NULL)
77         XFree((char*)protocols);
78 }
79
80
81 static WSizePolicy get_sizepolicy_winprop(WClientWin *cwin,
82                                           const char *propname,
83                                           WSizePolicy value)
84 {
85     char *szplcy;
86
87     if(extl_table_gets_s(cwin->proptab, propname, &szplcy)){
88         string2sizepolicy(szplcy, &value);
89         free(szplcy);
90     }
91     return value;
92 }
93
94
95 #define SIZEHINT_PROPS (CLIENTWIN_PROP_MAXSIZE|   \
96                         CLIENTWIN_PROP_MINSIZE|   \
97                         CLIENTWIN_PROP_ASPECT|    \
98                         CLIENTWIN_PROP_RSZINC|    \
99                         CLIENTWIN_PROP_I_MAXSIZE| \
100                         CLIENTWIN_PROP_I_MINSIZE| \
101                         CLIENTWIN_PROP_I_ASPECT|  \
102                         CLIENTWIN_PROP_I_RSZINC)
103
104
105 #define DO_SZH(NAME, FLAG, IFLAG, SZHFLAG, W, H, C)  \
106     if(extl_table_is_bool_set(tab, "ignore_" NAME)){ \
107         cwin->flags|=IFLAG;                          \
108     }else if(extl_table_gets_t(tab, NAME, &tab2)){   \
109         if(extl_table_gets_i(tab2, "w", &i1) &&      \
110            extl_table_gets_i(tab2, "h", &i2)){       \
111             cwin->size_hints.W=i1;                   \
112             cwin->size_hints.H=i2;                   \
113             C                                        \
114             cwin->size_hints.flags|=SZHFLAG;         \
115             cwin->flags|=FLAG;                       \
116         }                                            \
117         extl_unref_table(tab2);                      \
118     }
119
120
121 static void clientwin_get_winprops(WClientWin *cwin)
122 {
123     ExtlTab tab, tab2;
124     int i1, i2;
125     
126     tab=ioncore_get_winprop(cwin);
127     
128     cwin->proptab=tab;
129     
130     if(tab==extl_table_none())
131         return;
132
133     if(extl_table_is_bool_set(tab, "transparent"))
134         cwin->flags|=CLIENTWIN_PROP_TRANSPARENT;
135
136     if(extl_table_is_bool_set(tab, "acrobatic"))
137         cwin->flags|=CLIENTWIN_PROP_ACROBATIC;
138     
139     DO_SZH("max_size", CLIENTWIN_PROP_MAXSIZE, CLIENTWIN_PROP_I_MAXSIZE,
140            PMaxSize, max_width, max_height, );
141            
142     DO_SZH("min_size", CLIENTWIN_PROP_MINSIZE, CLIENTWIN_PROP_I_MINSIZE,
143            PMinSize, min_width, min_height, );
144            
145     DO_SZH("resizeinc", CLIENTWIN_PROP_RSZINC, CLIENTWIN_PROP_I_RSZINC,
146            PResizeInc, width_inc, height_inc, );
147
148     DO_SZH("aspect", CLIENTWIN_PROP_ASPECT, CLIENTWIN_PROP_I_ASPECT,
149            PAspect, min_aspect.x, min_aspect.y, 
150            { cwin->size_hints.max_aspect.x=i1;
151              cwin->size_hints.max_aspect.y=i2;
152            });
153            
154     if(extl_table_is_bool_set(tab, "ignore_cfgrq"))
155         cwin->flags|=CLIENTWIN_PROP_IGNORE_CFGRQ;
156
157 #if 0    
158     cwin->szplcy=get_sizepolicy_winprop(cwin, "sizepolicy", 
159                                         SIZEPOLICY_DEFAULT);
160     cwin->transient_szplcy=get_sizepolicy_winprop(cwin, 
161                                                   "transient_sizepolicy",
162                                                   DFLT_SZPLCY);
163 #endif
164 }
165
166
167 void clientwin_get_size_hints(WClientWin *cwin)
168 {
169     XSizeHints tmp=cwin->size_hints;
170     
171     xwindow_get_sizehints(cwin->win, &(cwin->size_hints));
172     
173     if(cwin->flags&CLIENTWIN_PROP_I_MAXSIZE){
174         cwin->size_hints.flags&=~PMaxSize;
175     }else if(cwin->flags&CLIENTWIN_PROP_MAXSIZE){
176         cwin->size_hints.max_width=tmp.max_width;
177         cwin->size_hints.max_height=tmp.max_height;
178         cwin->size_hints.flags|=PMaxSize;
179     }
180     
181     if(cwin->flags&CLIENTWIN_PROP_I_MINSIZE){
182         cwin->size_hints.flags&=~PMinSize;
183     }else if(cwin->flags&CLIENTWIN_PROP_MINSIZE){
184         cwin->size_hints.min_width=tmp.min_width;
185         cwin->size_hints.min_height=tmp.min_height;
186         cwin->size_hints.flags|=PMinSize;
187     }
188     
189     if(cwin->flags&CLIENTWIN_PROP_I_ASPECT){
190         cwin->size_hints.flags&=~PAspect;
191     }else if(cwin->flags&CLIENTWIN_PROP_ASPECT){
192         cwin->size_hints.min_aspect=tmp.min_aspect;
193         cwin->size_hints.max_aspect=tmp.max_aspect;
194         cwin->size_hints.flags|=PAspect;
195     }
196     
197     if(cwin->flags&CLIENTWIN_PROP_I_RSZINC){
198         cwin->size_hints.flags&=~PResizeInc;
199     }else if(cwin->flags&CLIENTWIN_PROP_RSZINC){
200         cwin->size_hints.width_inc=tmp.width_inc;
201         cwin->size_hints.height_inc=tmp.height_inc;
202         cwin->size_hints.flags|=PResizeInc;
203     }
204 }
205
206
207 void clientwin_get_set_name(WClientWin *cwin)
208 {
209     char **list=NULL;
210     int n=0;
211     
212     if(ioncore_g.use_mb)
213         list=netwm_get_name(cwin);
214
215     if(list==NULL){
216         list=xwindow_get_text_property(cwin->win, XA_WM_NAME, &n);
217     }else{
218         cwin->flags|=CLIENTWIN_USE_NET_WM_NAME;
219     }
220
221     if(list==NULL){
222         /* Special condition kludge: property exists, but couldn't
223          * be converted to a string list.
224          */
225         clientwin_set_name(cwin, (n==-1 ? "???" : NULL));
226     }else{
227         clientwin_set_name(cwin, *list);
228         XFreeStringList(list);
229     }
230 }
231
232
233 /* Some standard winprops */
234
235
236 bool clientwin_get_switchto(const WClientWin *cwin)
237 {
238     bool b;
239     
240     if(ioncore_g.opmode==IONCORE_OPMODE_INIT)
241         return FALSE;
242     
243     if(extl_table_gets_b(cwin->proptab, "switchto", &b))
244         return b;
245     
246     return ioncore_g.switchto_new;
247 }
248
249
250 int clientwin_get_transient_mode(const WClientWin *cwin)
251 {
252     char *s;
253     int mode=TRANSIENT_MODE_NORMAL;
254     
255     if(extl_table_gets_s(cwin->proptab, "transient_mode", &s)){
256         if(strcmp(s, "current")==0)
257             mode=TRANSIENT_MODE_CURRENT;
258         else if(strcmp(s, "off")==0)
259             mode=TRANSIENT_MODE_OFF;
260         free(s);
261     }
262     return mode;
263 }
264
265
266 /*}}}*/
267
268
269 /*{{{ Manage/create */
270
271
272 static void configure_cwin_bw(Window win, int bw)
273 {
274     XWindowChanges wc;
275     ulong wcmask=CWBorderWidth;
276     
277     wc.border_width=bw;
278     XConfigureWindow(ioncore_g.dpy, win, wcmask, &wc);
279 }
280
281
282 static void set_sane_gravity(Window win)
283 {
284     XSetWindowAttributes attr;
285     
286     attr.win_gravity=NorthWestGravity;
287     
288     XChangeWindowAttributes(ioncore_g.dpy, win,
289                             CWWinGravity, &attr);
290 }
291
292
293 static bool clientwin_init(WClientWin *cwin, WWindow *par, Window win,
294                            XWindowAttributes *attr)
295 {
296     WFitParams fp;
297
298     cwin->flags=0;
299     cwin->win=win;
300     cwin->state=WithdrawnState;
301     
302     fp.g.x=attr->x;
303     fp.g.y=attr->y;
304     fp.g.w=attr->width;
305     fp.g.h=attr->height;
306     fp.mode=REGION_FIT_EXACT;
307     
308     /* The idiot who invented special server-supported window borders that
309      * are not accounted for in the window size should be "taken behind a
310      * sauna".
311      */
312     cwin->orig_bw=attr->border_width;
313     configure_cwin_bw(cwin->win, 0);
314     if(cwin->orig_bw!=0 && cwin->size_hints.flags&PWinGravity){
315         fp.g.x+=xgravity_deltax(cwin->size_hints.win_gravity, 
316                                -cwin->orig_bw, -cwin->orig_bw);
317         fp.g.y+=xgravity_deltay(cwin->size_hints.win_gravity, 
318                                -cwin->orig_bw, -cwin->orig_bw);
319     }
320     
321     set_sane_gravity(cwin->win);
322
323     cwin->n_cmapwins=0;
324     cwin->cmap=attr->colormap;
325     cwin->cmaps=NULL;
326     cwin->cmapwins=NULL;
327     cwin->n_cmapwins=0;
328     cwin->event_mask=IONCORE_EVENTMASK_CLIENTWIN;
329
330     region_init(&(cwin->region), par, &fp);
331
332     cwin->region.flags|=REGION_GRAB_ON_PARENT;
333     region_add_bindmap(&cwin->region, ioncore_clientwin_bindmap);
334         
335     XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
336
337     clientwin_register(cwin);
338     clientwin_get_set_name(cwin);
339     clientwin_get_colormaps(cwin);
340     clientwin_get_protocols(cwin);
341     clientwin_get_winprops(cwin);
342     clientwin_get_size_hints(cwin);
343     
344     XSaveContext(ioncore_g.dpy, win, ioncore_g.win_context, (XPointer)cwin);
345     XAddToSaveSet(ioncore_g.dpy, win);
346
347     return TRUE;
348 }
349
350
351 static WClientWin *create_clientwin(WWindow *par, Window win,
352                                     XWindowAttributes *attr)
353 {
354     CREATEOBJ_IMPL(WClientWin, clientwin, (p, par, win, attr));
355 }
356
357
358
359 WClientWin *clientwin_get_transient_for(const WClientWin *cwin)
360 {
361     Window tforwin;
362     WClientWin *tfor=NULL;
363     
364     if(clientwin_get_transient_mode(cwin)!=TRANSIENT_MODE_NORMAL)
365         return NULL;
366
367     if(!XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin))
368         return NULL;
369     
370     if(tforwin==None)
371         return NULL;
372     
373     tfor=XWINDOW_REGION_OF_T(tforwin, WClientWin);
374     
375     if(tfor==cwin){
376         warn(TR("The transient_for hint for \"%s\" points to itself."),
377              region_name((WRegion*)cwin));
378     }else if(tfor==NULL){
379         if(xwindow_region_of(tforwin)!=NULL){
380             warn(TR("Client window \"%s\" has broken transient_for hint. "
381                     "(\"Extended WM hints\" multi-parent brain damage?)"),
382                  region_name((WRegion*)cwin));
383         }
384     }else if(!region_same_rootwin((WRegion*)cwin, (WRegion*)tfor)){
385         warn(TR("The transient_for window for \"%s\" is not on the same "
386                 "screen."), region_name((WRegion*)cwin));
387     }else{
388         return tfor;
389     }
390     
391     return NULL;
392 }
393
394
395 static bool postmanage_check(WClientWin *cwin, XWindowAttributes *attr)
396 {
397     /* Check that the window exists. The previous check and selectinput
398      * do not seem to catch all cases of window destroyal.
399      */
400     XSync(ioncore_g.dpy, False);
401     
402     if(XGetWindowAttributes(ioncore_g.dpy, cwin->win, attr))
403         return TRUE;
404     
405     warn(TR("Window %#x disappeared."), cwin->win);
406     
407     return FALSE;
408 }
409
410
411 static bool do_manage_mrsh(bool (*fn)(WClientWin *cwin, WManageParams *pm),
412                            void **p)
413 {
414     return fn((WClientWin*)p[0], (WManageParams*)p[1]);
415 }
416
417
418
419 static bool do_manage_mrsh_extl(ExtlFn fn, void **p)
420 {
421     WClientWin *cwin=(WClientWin*)p[0];
422     WManageParams *mp=(WManageParams*)p[1];
423     ExtlTab t=manageparams_to_table(mp);
424     bool ret=FALSE;
425     
426     extl_call(fn, "ot", "b", cwin, t, &ret);
427     
428     extl_unref_table(t);
429     
430     return (ret && REGION_MANAGER(cwin)!=NULL);
431 }
432
433
434 /* This is called when a window is mapped on the root window.
435  * We want to check if we should manage the window and how and
436  * act appropriately.
437  */
438 WClientWin* ioncore_manage_clientwin(Window win, bool maprq)
439 {
440     WRootWin *rootwin;
441     WClientWin *cwin=NULL;
442     XWindowAttributes attr;
443     XWMHints *hints;
444     int init_state=NormalState;
445     WManageParams param=MANAGEPARAMS_INIT;
446     void *mrshpm[2];
447
448     param.dockapp=FALSE;
449     
450 again:
451     /* Is the window already being managed? */
452     cwin=XWINDOW_REGION_OF_T(win, WClientWin);
453     if(cwin!=NULL)
454         return cwin;
455     
456     /* Select for UnmapNotify and DestroyNotify as the
457      * window might get destroyed or unmapped in the meanwhile.
458      */
459     xwindow_unmanaged_selectinput(win, StructureNotifyMask);
460
461     
462     /* Is it a dockapp?
463      */
464     hints=XGetWMHints(ioncore_g.dpy, win);
465
466     if(hints!=NULL && hints->flags&StateHint)
467         init_state=hints->initial_state;
468     
469     if(!param.dockapp && init_state==WithdrawnState && 
470        hints->flags&IconWindowHint && hints->icon_window!=None){
471         /* The dockapp might be displaying its "main" window if no
472          * wm that understands dockapps has been managing it.
473          */
474         if(!maprq)
475             XUnmapWindow(ioncore_g.dpy, win);
476         
477         xwindow_unmanaged_selectinput(win, 0);
478         
479         /* Copy WM_CLASS as _ION_DOCKAPP_HACK */
480         {
481             char **p=NULL;
482             int n=0;
483             
484             p=xwindow_get_text_property(win, XA_WM_CLASS, &n);
485             
486             if(p!=NULL){
487                 xwindow_set_text_property(hints->icon_window, 
488                                           ioncore_g.atom_dockapp_hack,
489                                           (const char **)p, n);
490                 XFreeStringList(p);
491             }else{
492                 const char *pdummy[2]={"unknowndockapp", "UnknownDockapp"};
493                 xwindow_set_text_property(hints->icon_window, 
494                                           ioncore_g.atom_dockapp_hack,
495                                           pdummy, 2);
496             }
497         }
498         
499         /* It is a dockapp, do everything again from the beginning, now
500          * with the icon window.
501          */
502         win=hints->icon_window;
503         param.dockapp=TRUE;
504         goto again;
505     }
506     
507     if(hints!=NULL)
508         XFree((void*)hints);
509
510     if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
511         if(maprq)
512             warn(TR("Window %#x disappeared."), win);
513         goto fail2;
514     }
515     
516     attr.width=maxof(attr.width, 1);
517     attr.height=maxof(attr.height, 1);
518
519     /* Do we really want to manage it? */
520     if(!param.dockapp && (attr.override_redirect || 
521         (!maprq && attr.map_state!=IsViewable))){
522         goto fail2;
523     }
524
525     /* Find root window */
526     FOR_ALL_ROOTWINS(rootwin){
527         if(WROOTWIN_ROOT(rootwin)==attr.root)
528             break;
529     }
530
531     if(rootwin==NULL){
532         warn(TR("Unable to find a matching root window!"));
533         goto fail2;
534     }
535
536     /* Allocate and initialize */
537     cwin=create_clientwin((WWindow*)rootwin, win, &attr);
538     
539     if(cwin==NULL){
540         warn_err();
541         goto fail2;
542     }
543
544     param.geom=REGION_GEOM(cwin);
545     param.maprq=maprq;
546     param.switchto=(init_state!=IconicState && clientwin_get_switchto(cwin));
547     param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
548     param.gravity=(cwin->size_hints.flags&PWinGravity
549                    ? cwin->size_hints.win_gravity
550                    : ForgetGravity);
551     param.tfor=clientwin_get_transient_for(cwin);
552     
553     if(!extl_table_gets_b(cwin->proptab, "userpos", &param.userpos))
554         param.userpos=(cwin->size_hints.flags&USPosition);
555     
556     if(cwin->flags&SIZEHINT_PROPS){
557         /* If size hints have been messed with, readjust requested geometry
558          * here. If programs themselves give incompatible geometries and
559          * things don't look good then, it's their fault.
560          */
561         region_size_hints_correct((WRegion*)cwin, &param.geom.w, &param.geom.h,
562                                   FALSE);
563     }
564
565     mrshpm[0]=cwin;
566     mrshpm[1]=&param;
567         
568     if(!hook_call_alt(clientwin_do_manage_alt, &mrshpm, 
569                       (WHookMarshall*)do_manage_mrsh,
570                       (WHookMarshallExtl*)do_manage_mrsh_extl)){
571         warn(TR("Unable to manage client window %#x."), win);
572         goto failure;
573     }
574     
575     if(ioncore_g.opmode==IONCORE_OPMODE_NORMAL &&
576        !region_is_fully_mapped((WRegion*)cwin) && 
577        !region_skip_focus((WRegion*)cwin)){
578         region_set_activity((WRegion*)cwin, SETPARAM_SET);
579     }
580     
581     if(postmanage_check(cwin, &attr)){
582         if(param.jumpto && ioncore_g.focus_next==NULL)
583             region_goto((WRegion*)cwin);
584         hook_call_o(clientwin_mapped_hook, (Obj*)cwin);
585         return cwin;
586     }
587
588 failure:
589     clientwin_destroyed(cwin);
590     return NULL;
591
592 fail2:
593     xwindow_unmanaged_selectinput(win, 0);
594     return NULL;
595 }
596
597
598 void clientwin_tfor_changed(WClientWin *cwin)
599 {
600 #if 0
601     WManageParams param=MANAGEPARAMS_INIT;
602     bool succeeded=FALSE;
603     param.tfor=clientwin_get_transient_for(cwin);
604     if(param.tfor==NULL)
605         return;
606     
607     region_rootpos((WRegion*)cwin, &(param.geom.x), &(param.geom.y));
608     param.geom.w=REGION_GEOM(cwin).w;
609     param.geom.h=REGION_GEOM(cwin).h;
610     param.maprq=FALSE;
611     param.userpos=FALSE;
612     param.switchto=region_may_control_focus((WRegion*)cwin);
613     param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
614     param.gravity=ForgetGravity;
615     
616     CALL_ALT_B(succeeded, clientwin_do_manage_alt, (cwin, &param));
617     warn("WM_TRANSIENT_FOR changed for \"%s\".",
618          region_name((WRegion*)cwin));
619 #else
620     warn(TR("Changes is WM_TRANSIENT_FOR property are unsupported."));
621 #endif        
622 }
623
624
625 /*}}}*/
626
627
628 /*{{{ Unmanage/destroy */
629
630
631 static bool reparent_root(WClientWin *cwin)
632 {
633     XWindowAttributes attr;
634     WWindow *par;
635     Window dummy;
636     int x=0, y=0;
637     
638     if(!XGetWindowAttributes(ioncore_g.dpy, cwin->win, &attr))
639         return FALSE;
640     
641     par=REGION_PARENT(cwin);
642     
643     if(par==NULL){
644         x=REGION_GEOM(cwin).x;
645         y=REGION_GEOM(cwin).y;
646     }else{
647         int dr=REGION_GEOM(par).w-REGION_GEOM(cwin).w-REGION_GEOM(cwin).x;
648         int db=REGION_GEOM(par).h-REGION_GEOM(cwin).h-REGION_GEOM(cwin).y;
649         dr=maxof(dr, 0);
650         db=maxof(db, 0);
651         
652         XTranslateCoordinates(ioncore_g.dpy, par->win, attr.root, 0, 0, 
653                               &x, &y, &dummy);
654
655         x-=xgravity_deltax(cwin->size_hints.win_gravity, 
656                            maxof(0, REGION_GEOM(cwin).x), dr);
657         y-=xgravity_deltay(cwin->size_hints.win_gravity, 
658                            maxof(0, REGION_GEOM(cwin).y), db);
659     }
660     
661     XReparentWindow(ioncore_g.dpy, cwin->win, attr.root, x, y);
662     
663     return TRUE;
664 }
665
666
667 void clientwin_deinit(WClientWin *cwin)
668 {
669     WRegion *reg;
670     
671     if(cwin->win!=None){
672         region_pointer_focus_hack(&cwin->region);
673
674         xwindow_unmanaged_selectinput(cwin->win, 0);
675         XUnmapWindow(ioncore_g.dpy, cwin->win);
676         
677         if(cwin->orig_bw!=0)
678             configure_cwin_bw(cwin->win, cwin->orig_bw);
679         
680         if(reparent_root(cwin)){
681             if(ioncore_g.opmode==IONCORE_OPMODE_DEINIT){
682                 XMapWindow(ioncore_g.dpy, cwin->win);
683                 /* Make sure the topmost window has focus; it doesn't really
684                  * matter which one has as long as some has.
685                  */
686                 xwindow_do_set_focus(cwin->win);
687             }else{
688                 set_clientwin_state(cwin, WithdrawnState);
689                 netwm_delete_state(cwin);
690             }
691         }
692         
693         XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
694         XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
695     }
696     
697     clientwin_clear_colormaps(cwin);
698     
699     region_deinit((WRegion*)cwin);
700 }
701
702
703
704 static bool mrsh_u_c(WHookDummy *fn, void *param)
705 {
706     fn(*(Window*)param);
707     return TRUE;
708 }
709
710 static bool mrsh_u_extl(ExtlFn fn, void *param)
711 {
712     double d=*(Window*)param;
713     extl_call(fn, "d", NULL, d);
714     return TRUE;
715 }
716
717 static void clientwin_do_unmapped(WClientWin *cwin, Window win)
718 {
719     cwin->flags|=CLIENTWIN_UNMAP_RQ;
720     
721     /* First try a graceful chain-dispose */
722     if(!region_rqdispose((WRegion*)cwin)){
723         /* But force dispose anyway */
724         region_dispose((WRegion*)cwin);
725     }
726     
727     hook_call(clientwin_unmapped_hook, &win, mrsh_u_c, mrsh_u_extl);
728 }
729
730 /* Used when the window was unmapped */
731 void clientwin_unmapped(WClientWin *cwin)
732 {
733     clientwin_do_unmapped(cwin, cwin->win);
734 }
735
736
737 /* Used when the window was deastroyed */
738 void clientwin_destroyed(WClientWin *cwin)
739 {
740     Window win=cwin->win;
741     XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
742     XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
743     xwindow_unmanaged_selectinput(cwin->win, 0);
744     cwin->win=None;
745     clientwin_do_unmapped(cwin, win);
746 }
747
748
749 /*}}}*/
750
751
752 /*{{{ Kill/close */
753
754
755 static bool send_clientmsg(Window win, Atom a, Time stmp)
756 {
757     XClientMessageEvent ev;
758     
759     ev.type=ClientMessage;
760     ev.window=win;
761     ev.message_type=ioncore_g.atom_wm_protocols;
762     ev.format=32;
763     ev.data.l[0]=a;
764     ev.data.l[1]=stmp;
765     
766     return (XSendEvent(ioncore_g.dpy, win, False, 0L, (XEvent*)&ev)!=0);
767 }
768
769
770 /*EXTL_DOC
771  * Attempt to kill (with \code{XKillWindow}) the client that owns 
772  * the X window correspoding to \var{cwin}.
773  */
774 EXTL_EXPORT_MEMBER
775 void clientwin_kill(WClientWin *cwin)
776 {
777     XKillClient(ioncore_g.dpy, cwin->win);
778 }
779
780
781 void clientwin_rqclose(WClientWin *cwin, bool relocate_ignored)
782 {
783     /* Ignore relocate parameter -- client windows can always be 
784      * destroyed by the application in any case, so way may just as
785      * well assume relocate is always set.
786      */
787     
788     if(cwin->flags&CLIENTWIN_P_WM_DELETE){
789         send_clientmsg(cwin->win, ioncore_g.atom_wm_delete, 
790                        ioncore_get_timestamp());
791     }else{
792         warn(TR("Client does not support the WM_DELETE protocol."));
793     }
794 }
795
796
797 /*}}}*/
798
799
800 /*{{{ State (hide/show) */
801
802
803 static void set_clientwin_state(WClientWin *cwin, int state)
804 {
805     if(cwin->state!=state){
806         cwin->state=state;
807         xwindow_set_state_property(cwin->win, state);
808     }
809 }
810
811
812 static void hide_clientwin(WClientWin *cwin)
813 {
814     region_pointer_focus_hack(&cwin->region);
815
816     if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
817         XMoveWindow(ioncore_g.dpy, cwin->win,
818                     -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h);
819         return;
820     }
821     
822     set_clientwin_state(cwin, IconicState);
823     XSelectInput(ioncore_g.dpy, cwin->win,
824                  cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
825     XUnmapWindow(ioncore_g.dpy, cwin->win);
826     XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
827 }
828
829
830 static void show_clientwin(WClientWin *cwin)
831 {
832     if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
833         XMoveWindow(ioncore_g.dpy, cwin->win,
834                     REGION_GEOM(cwin).x, REGION_GEOM(cwin).y);
835         if(cwin->state==NormalState)
836             return;
837     }
838     
839     XSelectInput(ioncore_g.dpy, cwin->win,
840                  cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
841     XMapWindow(ioncore_g.dpy, cwin->win);
842     XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
843     set_clientwin_state(cwin, NormalState);
844 }
845
846
847 /*}}}*/
848
849
850 /*{{{ Resize/reparent/reconf helpers */
851
852
853 void clientwin_notify_rootpos(WClientWin *cwin, int rootx, int rooty)
854 {
855     XEvent ce;
856     Window win;
857     
858     if(cwin==NULL)
859         return;
860     
861     win=cwin->win;
862     
863     ce.xconfigure.type=ConfigureNotify;
864     ce.xconfigure.event=win;
865     ce.xconfigure.window=win;
866     ce.xconfigure.x=rootx-cwin->orig_bw;
867     ce.xconfigure.y=rooty-cwin->orig_bw;
868     ce.xconfigure.width=REGION_GEOM(cwin).w;
869     ce.xconfigure.height=REGION_GEOM(cwin).h;
870     ce.xconfigure.border_width=cwin->orig_bw;
871     ce.xconfigure.above=None;
872     ce.xconfigure.override_redirect=False;
873     
874     XSelectInput(ioncore_g.dpy, win, cwin->event_mask&~StructureNotifyMask);
875     XSendEvent(ioncore_g.dpy, win, False, StructureNotifyMask, &ce);
876     XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
877 }
878
879
880 static void sendconfig_clientwin(WClientWin *cwin)
881 {
882     int rootx, rooty;
883     
884     region_rootpos(&cwin->region, &rootx, &rooty);
885     clientwin_notify_rootpos(cwin, rootx, rooty);
886 }
887
888
889 static void do_reparent_clientwin(WClientWin *cwin, Window win, int x, int y)
890 {
891     XSelectInput(ioncore_g.dpy, cwin->win,
892                  cwin->event_mask&~StructureNotifyMask);
893     XReparentWindow(ioncore_g.dpy, cwin->win, win, x, y);
894     XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
895 }
896
897
898 static void convert_geom(const WFitParams *fp, 
899                          WClientWin *cwin, WRectangle *geom)
900 {
901     WFitParams fptmp=*fp;
902     WSizePolicy szplcy=SIZEPOLICY_FULL_EXACT;
903     
904     /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
905         szplcy=cwin->szplcy;*/
906     
907     sizepolicy(&szplcy, (WRegion*)cwin, NULL, REGION_RQGEOM_WEAK_ALL, &fptmp);
908     
909     *geom=fptmp.g;
910 }
911
912
913 /*}}}*/
914
915
916 /*{{{ Region dynfuns */
917
918
919 static bool clientwin_fitrep(WClientWin *cwin, WWindow *np, 
920                              const WFitParams *fp)
921 {
922     WRectangle geom;
923     bool changes;
924     int w, h;
925
926     if(np!=NULL && !region_same_rootwin((WRegion*)cwin, (WRegion*)np))
927         return FALSE;
928     
929     if(fp->mode&REGION_FIT_WHATEVER){
930         geom.x=fp->g.x;
931         geom.y=fp->g.y;
932         geom.w=REGION_GEOM(cwin).w;
933         geom.h=REGION_GEOM(cwin).h;
934     }else{
935         geom=fp->g;
936     }
937     
938     changes=(REGION_GEOM(cwin).x!=geom.x ||
939              REGION_GEOM(cwin).y!=geom.y ||
940              REGION_GEOM(cwin).w!=geom.w ||
941              REGION_GEOM(cwin).h!=geom.h);
942     
943     REGION_GEOM(cwin)=geom;
944     
945     if(np==NULL && !changes)
946         return TRUE;
947     
948     if(np!=NULL){
949         region_unset_parent((WRegion*)cwin);
950         do_reparent_clientwin(cwin, np->win, geom.x, geom.y);
951         region_set_parent((WRegion*)cwin, np);
952         sendconfig_clientwin(cwin);
953         
954         if(!REGION_IS_FULLSCREEN(cwin))
955             cwin->flags&=~CLIENTWIN_FS_RQ;
956
957         netwm_update_state(cwin);
958     }
959     
960     w=maxof(1, geom.w);
961     h=maxof(1, geom.h);
962     
963     if(cwin->flags&CLIENTWIN_PROP_ACROBATIC && !REGION_IS_MAPPED(cwin)){
964         XMoveResizeWindow(ioncore_g.dpy, cwin->win,
965                           -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h, 
966                           w, h);
967     }else{
968         XMoveResizeWindow(ioncore_g.dpy, cwin->win, geom.x, geom.y, w, h);
969     }
970     
971     cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
972     
973     return TRUE;
974 }
975
976
977 static void clientwin_map(WClientWin *cwin)
978 {
979     show_clientwin(cwin);
980     REGION_MARK_MAPPED(cwin);
981 }
982
983
984 static void clientwin_unmap(WClientWin *cwin)
985 {
986     hide_clientwin(cwin);
987     REGION_MARK_UNMAPPED(cwin);
988 }
989
990
991 static void clientwin_do_set_focus(WClientWin *cwin, bool warp)
992 {
993     if(cwin->flags&CLIENTWIN_P_WM_TAKE_FOCUS){
994         Time stmp=ioncore_get_timestamp();
995         send_clientmsg(cwin->win, ioncore_g.atom_wm_take_focus, stmp);
996     }
997
998     region_finalise_focusing((WRegion*)cwin, cwin->win, warp);
999     
1000     XSync(ioncore_g.dpy, 0);
1001 }
1002
1003
1004 void clientwin_restack(WClientWin *cwin, Window other, int mode)
1005 {
1006     xwindow_restack(cwin->win, other, mode);
1007 }
1008        
1009
1010 void clientwin_stacking(WClientWin *cwin, Window *bottomret, Window *topret)
1011 {
1012     *bottomret=cwin->win;
1013     *topret=cwin->win;
1014 }
1015
1016
1017 static Window clientwin_x_window(WClientWin *cwin)
1018 {
1019     return cwin->win;
1020 }
1021
1022
1023 static void clientwin_activated(WClientWin *cwin)
1024 {
1025     clientwin_install_colormap(cwin);
1026 }
1027
1028
1029 static void clientwin_size_hints(WClientWin *cwin, WSizeHints *hints_ret)
1030 {
1031     if(cwin->flags&CLIENTWIN_FS_RQ){
1032         /* Do not use size hints, when full screen mode has been
1033          * requested by the client window itself.
1034          */
1035         sizehints_clear(hints_ret);
1036     }else{
1037         xsizehints_to_sizehints(&cwin->size_hints, hints_ret);
1038     }
1039 }
1040
1041
1042 /*}}}*/
1043
1044
1045 /*{{{ Identity & lookup */
1046
1047
1048 /*EXTL_DOC
1049  * Returns a table containing the properties \code{WM_CLASS} (table entries
1050  * \var{instance} and \var{class}) and  \code{WM_WINDOW_ROLE} (\var{role})
1051  * properties for \var{cwin}. If a property is not set, the corresponding 
1052  * field(s) are unset in the  table.
1053  */
1054 EXTL_SAFE
1055 EXTL_EXPORT_MEMBER
1056 ExtlTab clientwin_get_ident(WClientWin *cwin)
1057 {
1058     char **p=NULL, **p2=NULL, *wrole=NULL;
1059     int n=0, n2=0, n3=0, tmp=0;
1060     Window tforwin=None;
1061     ExtlTab tab;
1062     bool dockapp_hack=FALSE;
1063     
1064     p=xwindow_get_text_property(cwin->win, XA_WM_CLASS, &n);
1065     
1066     p2=xwindow_get_text_property(cwin->win, ioncore_g.atom_dockapp_hack, &n2);
1067     
1068     dockapp_hack=(n2>0);
1069     
1070     if(p==NULL){
1071         /* Some dockapps do actually have WM_CLASS, so use it. */
1072         p=p2;
1073         n=n2;
1074         p2=NULL;
1075     }
1076     
1077     wrole=xwindow_get_string_property(cwin->win, ioncore_g.atom_wm_window_role, 
1078                                       &n3);
1079     
1080     tab=extl_create_table();
1081     if(n>=2 && p[1]!=NULL)
1082         extl_table_sets_s(tab, "class", p[1]);
1083     if(n>=1 && p[0]!=NULL)
1084         extl_table_sets_s(tab, "instance", p[0]);
1085     if(wrole!=NULL)
1086         extl_table_sets_s(tab, "role", wrole);
1087     
1088     if(XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin) 
1089        && tforwin!=None){
1090         extl_table_sets_b(tab, "is_transient", TRUE);
1091     }
1092     
1093     if(dockapp_hack)
1094         extl_table_sets_b(tab, "is_dockapp", TRUE);
1095     
1096     if(p!=NULL)
1097         XFreeStringList(p);
1098     if(p2!=NULL)
1099         XFreeStringList(p2);
1100     if(wrole!=NULL)
1101         free(wrole);
1102     
1103     return tab;
1104 }
1105
1106
1107 /*}}}*/
1108
1109
1110 /*{{{ ConfigureRequest */
1111
1112
1113 static bool check_fs_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1114 {
1115     /* check full screen request */
1116     if((ev->value_mask&(CWWidth|CWHeight))==(CWWidth|CWHeight)){
1117         WRegion *grp=region_groupleader_of((WRegion*)cwin);
1118         WScreen *scr=clientwin_fullscreen_chkrq(cwin, ev->width, ev->height);
1119         
1120         if(scr!=NULL && REGION_MANAGER(grp)!=(WRegion*)scr){
1121             bool sw=clientwin_fullscreen_may_switchto(cwin);
1122             
1123             cwin->flags|=CLIENTWIN_FS_RQ;
1124             
1125             if(!region_fullscreen_scr(grp, scr, sw))
1126                 cwin->flags&=~CLIENTWIN_FS_RQ;
1127                 
1128             return TRUE;
1129         }
1130     }
1131
1132     return FALSE;
1133 }
1134
1135
1136 static bool check_normal_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1137 {
1138     if(ev->value_mask&(CWX|CWY|CWWidth|CWHeight)){
1139         WRQGeomParams rq=RQGEOMPARAMS_INIT;
1140         int gdx=0, gdy=0;
1141
1142         rq.flags=REGION_RQGEOM_WEAK_ALL|REGION_RQGEOM_ABSOLUTE;
1143         
1144         if(cwin->size_hints.flags&PWinGravity){
1145             rq.flags|=REGION_RQGEOM_GRAVITY;
1146             rq.gravity=cwin->size_hints.win_gravity;
1147         }
1148         
1149         /* Do I need to insert another disparaging comment on the person who
1150          * invented special server-supported window borders that are not 
1151          * accounted for in the window size? Keep it simple, stupid!
1152          */
1153         if(cwin->size_hints.flags&PWinGravity){
1154             gdx=xgravity_deltax(cwin->size_hints.win_gravity, 
1155                                -cwin->orig_bw, -cwin->orig_bw);
1156             gdy=xgravity_deltay(cwin->size_hints.win_gravity, 
1157                                -cwin->orig_bw, -cwin->orig_bw);
1158         }
1159         
1160         region_rootpos((WRegion*)cwin, &(rq.geom.x), &(rq.geom.y));
1161         rq.geom.w=REGION_GEOM(cwin).w;
1162         rq.geom.h=REGION_GEOM(cwin).h;
1163         
1164         if(ev->value_mask&CWWidth){
1165             /* If x was not changed, keep reference point where it was */
1166             if(cwin->size_hints.flags&PWinGravity){
1167                 rq.geom.x+=xgravity_deltax(cwin->size_hints.win_gravity, 0,
1168                                            ev->width-rq.geom.w);
1169             }
1170             rq.geom.w=maxof(ev->width, 1);
1171             rq.flags&=~REGION_RQGEOM_WEAK_W;
1172         }
1173         if(ev->value_mask&CWHeight){
1174             /* If y was not changed, keep reference point where it was */
1175             if(cwin->size_hints.flags&PWinGravity){
1176                 rq.geom.y+=xgravity_deltay(cwin->size_hints.win_gravity, 0,
1177                                            ev->height-rq.geom.h);
1178             }
1179             rq.geom.h=maxof(ev->height, 1);
1180             rq.flags&=~REGION_RQGEOM_WEAK_H;
1181         }
1182         if(ev->value_mask&CWX){
1183             rq.geom.x=ev->x+gdx;
1184             rq.flags&=~REGION_RQGEOM_WEAK_X;
1185         }
1186         if(ev->value_mask&CWY){
1187             rq.geom.y=ev->y+gdy;
1188             rq.flags&=~REGION_RQGEOM_WEAK_Y;
1189         }
1190         
1191         region_rqgeom((WRegion*)cwin, &rq, NULL);
1192         
1193         return TRUE;
1194     }
1195     
1196     return FALSE;
1197 }
1198
1199
1200 void clientwin_handle_configure_request(WClientWin *cwin,
1201                                         XConfigureRequestEvent *ev)
1202 {
1203     if(ev->value_mask&CWBorderWidth)
1204         cwin->orig_bw=ev->border_width;
1205     
1206     cwin->flags|=CLIENTWIN_NEED_CFGNTFY;
1207
1208     if(!(cwin->flags&CLIENTWIN_PROP_IGNORE_CFGRQ)){
1209         if(!check_fs_cfgrq(cwin, ev))
1210             check_normal_cfgrq(cwin, ev);
1211     }
1212
1213     if(cwin->flags&CLIENTWIN_NEED_CFGNTFY){
1214         sendconfig_clientwin(cwin);
1215         cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1216     }
1217 }
1218
1219
1220 /*}}}*/
1221
1222
1223 /*{{{ Kludges */
1224
1225
1226 /*EXTL_DOC
1227  * Attempts to fix window size problems with non-ICCCM compliant
1228  * programs.
1229  */
1230 EXTL_EXPORT_MEMBER
1231 void clientwin_nudge(WClientWin *cwin)
1232 {
1233     XResizeWindow(ioncore_g.dpy, cwin->win, 
1234                   2*REGION_GEOM(cwin).w, 2*REGION_GEOM(cwin).h);
1235     XFlush(ioncore_g.dpy);
1236     XResizeWindow(ioncore_g.dpy, cwin->win, 
1237                   REGION_GEOM(cwin).w, REGION_GEOM(cwin).h);
1238 }
1239
1240
1241 /*}}}*/
1242
1243
1244 /*{{{ Misc. */
1245
1246
1247 /*EXTL_DOC
1248  * Return the X window id for the client window.
1249  */
1250 EXTL_SAFE
1251 EXTL_EXPORT_MEMBER
1252 double clientwin_xid(WClientWin *cwin)
1253 {
1254     return cwin->win;
1255 }
1256
1257
1258 /*}}}*/
1259
1260
1261 /*{{{ Save/load */
1262
1263
1264 static int last_checkcode=1;
1265
1266
1267 static ExtlTab clientwin_get_configuration(WClientWin *cwin)
1268 {
1269     int chkc=0;
1270     ExtlTab tab;
1271     SMCfgCallback *cfg_cb;
1272     SMAddCallback *add_cb;
1273     
1274     tab=region_get_base_configuration((WRegion*)cwin);
1275
1276     extl_table_sets_d(tab, "windowid", (double)(cwin->win));
1277     
1278     if(last_checkcode!=0){
1279         chkc=last_checkcode++;
1280         xwindow_set_integer_property(cwin->win, ioncore_g.atom_checkcode, 
1281                                      chkc);
1282         extl_table_sets_i(tab, "checkcode", chkc);
1283     }
1284
1285     ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1286     
1287     if(cfg_cb!=NULL)
1288         cfg_cb(cwin, tab);
1289     
1290     return tab;
1291 }
1292
1293
1294 WRegion *clientwin_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1295 {
1296     double wind=0;
1297     Window win=None;
1298     int chkc=0, real_chkc=0;
1299     WClientWin *cwin=NULL;
1300     XWindowAttributes attr;
1301     WRectangle rg;
1302     bool got_chkc=FALSE;
1303     
1304     if(!extl_table_gets_d(tab, "windowid", &wind) ||
1305        !extl_table_gets_i(tab, "checkcode", &chkc)){
1306         return NULL;
1307     }
1308     
1309     win=(Window)wind;
1310
1311     if(XWINDOW_REGION_OF(win)!=NULL){
1312         warn("Client window %x already managed.", win);
1313         return NULL;
1314     }
1315
1316     got_chkc=xwindow_get_integer_property(win, ioncore_g.atom_checkcode, 
1317                                           &real_chkc);
1318     
1319     if(!got_chkc || real_chkc!=chkc){
1320         ioncore_clientwin_load_missing();
1321         return NULL;
1322     }
1323
1324     /* Found it! */
1325     
1326     if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
1327         warn(TR("Window %#x disappeared."), win);
1328         return NULL;
1329     }
1330     
1331     if(attr.override_redirect || 
1332        (ioncore_g.opmode==IONCORE_OPMODE_INIT && attr.map_state!=IsViewable)){
1333         warn(TR("Saved client window does not want to be managed."));
1334         return NULL;
1335     }
1336     
1337     cwin=create_clientwin(par, win, &attr);
1338     
1339     if(cwin==NULL)
1340         return FALSE;
1341     
1342     /* Reparent and resize taking limits set by size hints into account */
1343     convert_geom(fp, cwin, &rg);
1344     REGION_GEOM(cwin)=rg;
1345     do_reparent_clientwin(cwin, par->win, rg.x, rg.y);
1346     XResizeWindow(ioncore_g.dpy, win, maxof(1, rg.w), maxof(1, rg.h));
1347     
1348     if(!postmanage_check(cwin, &attr)){
1349         clientwin_destroyed(cwin);
1350         return NULL;
1351     }
1352     
1353     return (WRegion*)cwin;
1354 }
1355
1356
1357 /*}}}*/
1358
1359
1360 /*{{{ Dynfuntab and class info */
1361
1362
1363 static DynFunTab clientwin_dynfuntab[]={
1364     {(DynFun*)region_fitrep,
1365      (DynFun*)clientwin_fitrep},
1366
1367     {region_map,
1368      clientwin_map},
1369     
1370     {region_unmap,
1371      clientwin_unmap},
1372     
1373     {region_do_set_focus, 
1374      clientwin_do_set_focus},
1375     
1376     {region_notify_rootpos, 
1377      clientwin_notify_rootpos},
1378     
1379     {region_restack, 
1380      clientwin_restack},
1381
1382     {region_stacking, 
1383      clientwin_stacking},
1384     
1385     {(DynFun*)region_xwindow, 
1386      (DynFun*)clientwin_x_window},
1387     
1388     {region_activated, 
1389      clientwin_activated},
1390     
1391     {region_size_hints, 
1392      clientwin_size_hints},
1393     
1394     {(DynFun*)region_rqclose, 
1395      (DynFun*)clientwin_rqclose},
1396     
1397     {(DynFun*)region_get_configuration,
1398      (DynFun*)clientwin_get_configuration},
1399
1400     END_DYNFUNTAB
1401 };
1402
1403
1404 EXTL_EXPORT
1405 IMPLCLASS(WClientWin, WRegion, clientwin_deinit, clientwin_dynfuntab);
1406
1407
1408 /*}}}*/