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