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