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