Consistency fixes
[notion.git] / ioncore / clientwin.c
blobcbc3a23033644ac8f0d137dec64aef204c683865
1 /*
2 * ion/ioncore/clientwin.c
4 * Copyright (c) Tuomo Valkonen 1999-2009.
6 * See the included file LICENSE for details.
7 */
9 #include <string.h>
10 #include <limits.h>
11 #include <ctype.h>
13 #include <libtu/objp.h>
14 #include <libtu/minmax.h>
15 #include <libextl/extl.h>
16 #include <libmainloop/defer.h>
17 #include <libmainloop/hooks.h>
18 #include "common.h"
19 #include "global.h"
20 #include "property.h"
21 #include "focus.h"
22 #include "sizehint.h"
23 #include "event.h"
24 #include "clientwin.h"
25 #include "colormap.h"
26 #include "resize.h"
27 #include "attach.h"
28 #include "regbind.h"
29 #include "bindmaps.h"
30 #include "names.h"
31 #include "saveload.h"
32 #include "manage.h"
33 #include "extlconv.h"
34 #include "fullscreen.h"
35 #include "event.h"
36 #include "rootwin.h"
37 #include "activity.h"
38 #include "netwm.h"
39 #include "xwindow.h"
40 #include "bindmaps.h"
41 #include "return.h"
42 #include "conf.h"
43 #include "group.h"
46 static void set_clientwin_state(WClientWin *cwin, int state);
47 static bool send_clientmsg(Window win, Atom a, Time stmp);
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;
56 /*{{{ Get properties */
59 void clientwin_get_protocols(WClientWin *cwin)
61 Atom *protocols=NULL, *p;
62 int n;
64 cwin->flags&=~(CLIENTWIN_P_WM_DELETE|CLIENTWIN_P_WM_TAKE_FOCUS);
66 if(!XGetWMProtocols(ioncore_g.dpy, cwin->win, &protocols, &n))
67 return;
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;
76 if(protocols!=NULL)
77 XFree((char*)protocols);
81 #define SIZEHINT_PROPS (CLIENTWIN_PROP_MAXSIZE| \
82 CLIENTWIN_PROP_MINSIZE| \
83 CLIENTWIN_PROP_ASPECT| \
84 CLIENTWIN_PROP_RSZINC| \
85 CLIENTWIN_PROP_I_MAXSIZE| \
86 CLIENTWIN_PROP_I_MINSIZE| \
87 CLIENTWIN_PROP_I_ASPECT| \
88 CLIENTWIN_PROP_I_RSZINC)
91 #define DO_SZH(NAME, FLAG, IFLAG, SZHFLAG, W, H, C) \
92 if(extl_table_is_bool_set(tab, "ignore_" NAME)){ \
93 cwin->flags|=IFLAG; \
94 }else if(extl_table_gets_t(tab, NAME, &tab2)){ \
95 if(extl_table_gets_i(tab2, "w", &i1) && \
96 extl_table_gets_i(tab2, "h", &i2)){ \
97 cwin->size_hints.W=i1; \
98 cwin->size_hints.H=i2; \
99 C \
100 cwin->size_hints.flags|=SZHFLAG; \
101 cwin->flags|=FLAG; \
103 extl_unref_table(tab2); \
107 static void clientwin_get_winprops(WClientWin *cwin)
109 ExtlTab tab, tab2;
110 char *s;
111 int i1, i2;
113 tab=ioncore_get_winprop(cwin);
115 cwin->proptab=tab;
117 if(tab==extl_table_none())
118 return;
120 if(extl_table_is_bool_set(tab, "transparent"))
121 cwin->flags|=CLIENTWIN_PROP_TRANSPARENT;
123 if(extl_table_is_bool_set(tab, "acrobatic"))
124 cwin->flags|=CLIENTWIN_PROP_ACROBATIC;
126 if(extl_table_is_bool_set(tab, "lazy_resize"))
127 cwin->flags|=CLIENTWIN_PROP_LAZY_RESIZE;
129 DO_SZH("max_size", CLIENTWIN_PROP_MAXSIZE, CLIENTWIN_PROP_I_MAXSIZE,
130 PMaxSize, max_width, max_height, { });
132 DO_SZH("min_size", CLIENTWIN_PROP_MINSIZE, CLIENTWIN_PROP_I_MINSIZE,
133 PMinSize, min_width, min_height, { });
135 DO_SZH("resizeinc", CLIENTWIN_PROP_RSZINC, CLIENTWIN_PROP_I_RSZINC,
136 PResizeInc, width_inc, height_inc, { });
138 DO_SZH("aspect", CLIENTWIN_PROP_ASPECT, CLIENTWIN_PROP_I_ASPECT,
139 PAspect, min_aspect.x, min_aspect.y,
140 { cwin->size_hints.max_aspect.x=i1;
141 cwin->size_hints.max_aspect.y=i2;
144 if(extl_table_is_bool_set(tab, "ignore_cfgrq"))
145 cwin->flags|=CLIENTWIN_PROP_IGNORE_CFGRQ;
147 if(extl_table_gets_s(tab, "orientation", &s)){
148 if(strcmp(s, "vertical")==0)
149 cwin->flags|=CLIENTWIN_PROP_O_VERT;
150 else if(strcmp(s, "horizontal")==0)
151 cwin->flags|=CLIENTWIN_PROP_O_HORIZ;
152 free(s);
156 void clientwin_apply_size_hint_winprops(XSizeHints original, WClientWin *cwin)
158 if(cwin->flags&CLIENTWIN_PROP_I_MAXSIZE){
159 cwin->size_hints.flags&=~PMaxSize;
160 }else if(cwin->flags&CLIENTWIN_PROP_MAXSIZE){
161 cwin->size_hints.max_width=original.max_width;
162 cwin->size_hints.max_height=original.max_height;
163 cwin->size_hints.flags|=PMaxSize;
166 if(cwin->flags&CLIENTWIN_PROP_I_MINSIZE){
167 cwin->size_hints.flags&=~PMinSize;
168 }else if(cwin->flags&CLIENTWIN_PROP_MINSIZE){
169 cwin->size_hints.min_width=original.min_width;
170 cwin->size_hints.min_height=original.min_height;
171 cwin->size_hints.flags|=PMinSize;
174 if(cwin->flags&CLIENTWIN_PROP_I_ASPECT){
175 cwin->size_hints.flags&=~PAspect;
176 }else if(cwin->flags&CLIENTWIN_PROP_ASPECT){
177 cwin->size_hints.min_aspect=original.min_aspect;
178 cwin->size_hints.max_aspect=original.max_aspect;
179 cwin->size_hints.flags|=PAspect;
182 if(cwin->flags&CLIENTWIN_PROP_I_RSZINC){
183 cwin->size_hints.flags&=~PResizeInc;
184 }else if(cwin->flags&CLIENTWIN_PROP_RSZINC){
185 cwin->size_hints.width_inc=original.width_inc;
186 cwin->size_hints.height_inc=original.height_inc;
187 cwin->size_hints.flags|=PResizeInc;
191 int clientwin_get_size_hints(WClientWin *cwin)
193 XSizeHints original=cwin->size_hints;
195 int ret=xwindow_get_sizehints(cwin->win, &(cwin->size_hints));
197 clientwin_apply_size_hint_winprops(original, cwin);
199 return ret;
202 void clientwin_reset_size_hints(WClientWin *cwin)
204 XSizeHints original=cwin->size_hints;
206 memset(&(cwin->size_hints), 0, sizeof(cwin->size_hints));
207 clientwin_apply_size_hint_winprops(original, cwin);
210 void clientwin_get_input_wm_hint(WClientWin *cwin)
212 XWMHints *hints;
214 hints=XGetWMHints(ioncore_g.dpy, cwin->win);
216 cwin->flags|=CLIENTWIN_SET_INPUT;
217 if(hints!=NULL){
218 if(hints->flags&InputHint && !hints->input)
219 cwin->flags&=~CLIENTWIN_SET_INPUT;
220 XFree((void*)hints);
224 void clientwin_get_set_name(WClientWin *cwin)
226 char **list=NULL;
227 int n=0;
229 if(ioncore_g.use_mb)
230 list=netwm_get_name(cwin);
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;
238 if(list==NULL){
239 /* Special condition kludge: property exists, but couldn't
240 * be converted to a string list.
242 clientwin_set_name(cwin, (n==-1 ? "???" : NULL));
243 }else{
244 clientwin_set_name(cwin, *list);
245 XFreeStringList(list);
250 /* Some standard winprops */
253 bool clientwin_get_switchto(const WClientWin *cwin)
255 bool b;
257 if(ioncore_g.opmode==IONCORE_OPMODE_INIT)
258 return FALSE;
260 if(extl_table_gets_b(cwin->proptab, "switchto", &b))
261 return b;
263 return ioncore_g.switchto_new;
267 int clientwin_get_transient_mode(const WClientWin *cwin)
269 char *s;
270 int mode=TRANSIENT_MODE_NORMAL;
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);
279 return mode;
283 /*}}}*/
286 /*{{{ Manage/create */
289 static void configure_cwin_bw(Window win, int bw)
291 XWindowChanges wc;
292 ulong wcmask=CWBorderWidth;
294 wc.border_width=bw;
295 XConfigureWindow(ioncore_g.dpy, win, wcmask, &wc);
299 static void set_sane_gravity(Window win)
301 XSetWindowAttributes attr;
303 attr.win_gravity=NorthWestGravity;
305 XChangeWindowAttributes(ioncore_g.dpy, win,
306 CWWinGravity, &attr);
310 static bool clientwin_init(WClientWin *cwin, WWindow *par, Window win,
311 XWindowAttributes *attr)
313 WFitParams fp;
315 cwin->flags=0;
316 cwin->win=win;
317 cwin->state=WithdrawnState;
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;
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".
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);
338 set_sane_gravity(cwin->win);
340 cwin->n_cmapwins=0;
341 cwin->cmap=attr->colormap;
342 cwin->cmaps=NULL;
343 cwin->cmapwins=NULL;
344 cwin->n_cmapwins=0;
345 cwin->event_mask=IONCORE_EVENTMASK_CLIENTWIN;
347 region_init(&(cwin->region), par, &fp);
349 cwin->region.flags|=REGION_GRAB_ON_PARENT;
350 region_add_bindmap(&cwin->region, ioncore_clientwin_bindmap);
352 XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
354 clientwin_register(cwin);
355 clientwin_get_set_name(cwin);
356 clientwin_get_colormaps(cwin);
357 clientwin_get_protocols(cwin);
358 clientwin_get_winprops(cwin);
359 clientwin_get_size_hints(cwin);
360 clientwin_get_input_wm_hint(cwin);
362 netwm_update_allowed_actions(cwin);
364 XSaveContext(ioncore_g.dpy, win, ioncore_g.win_context, (XPointer)cwin);
365 XAddToSaveSet(ioncore_g.dpy, win);
367 return TRUE;
371 static WClientWin *create_clientwin(WWindow *par, Window win,
372 XWindowAttributes *attr)
374 CREATEOBJ_IMPL(WClientWin, clientwin, (p, par, win, attr));
379 WClientWin *clientwin_get_transient_for(const WClientWin *cwin)
381 Window tforwin;
382 WClientWin *tfor=NULL;
384 if(clientwin_get_transient_mode(cwin)!=TRANSIENT_MODE_NORMAL)
385 return NULL;
387 if(!XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin))
388 return NULL;
390 if(tforwin==None)
391 return NULL;
393 tfor=XWINDOW_REGION_OF_T(tforwin, WClientWin);
395 if(tfor==cwin){
396 warn(TR("The transient_for hint for \"%s\" points to itself."),
397 region_name((WRegion*)cwin));
398 }else if(tfor==NULL){
399 if(xwindow_region_of(tforwin)!=NULL){
400 warn(TR("Client window \"%s\" has broken transient_for hint. "
401 "(\"Extended WM hints\" multi-parent brain damage?)"),
402 region_name((WRegion*)cwin));
404 }else if(!region_same_rootwin((WRegion*)cwin, (WRegion*)tfor)){
405 warn(TR("The transient_for window for \"%s\" is not on the same "
406 "screen."), region_name((WRegion*)cwin));
407 }else{
408 return tfor;
411 return NULL;
415 static bool postmanage_check(WClientWin *cwin, XWindowAttributes *attr)
417 /* Check that the window exists. The previous check and selectinput
418 * do not seem to catch all cases of window destroyal.
420 XSync(ioncore_g.dpy, False);
422 if(XGetWindowAttributes(ioncore_g.dpy, cwin->win, attr))
423 return TRUE;
425 warn(TR("Window %#x disappeared."), cwin->win);
427 return FALSE;
431 static bool do_manage_mrsh(bool (*fn)(WClientWin *cwin, WManageParams *pm),
432 void **p)
434 return fn((WClientWin*)p[0], (WManageParams*)p[1]);
439 static bool do_manage_mrsh_extl(ExtlFn fn, void **p)
441 WClientWin *cwin=(WClientWin*)p[0];
442 WManageParams *mp=(WManageParams*)p[1];
443 ExtlTab t=manageparams_to_table(mp);
444 bool ret=FALSE;
446 extl_call(fn, "ot", "b", cwin, t, &ret);
448 extl_unref_table(t);
450 return (ret && REGION_MANAGER(cwin)!=NULL);
454 /* This is called when a window is mapped on the root window.
455 * We want to check if we should manage the window and how and
456 * act appropriately.
458 WClientWin* ioncore_manage_clientwin(Window win, bool maprq)
460 WRootWin *rootwin;
461 WClientWin *cwin=NULL;
462 XWindowAttributes attr;
463 XWMHints *hints;
464 int init_state=NormalState;
465 WManageParams param=MANAGEPARAMS_INIT;
466 void *mrshpm[2];
468 param.dockapp=FALSE;
470 /* Is the window already being managed? */
471 cwin=XWINDOW_REGION_OF_T(win, WClientWin);
472 if(cwin!=NULL)
473 return cwin;
475 /* Select for UnmapNotify and DestroyNotify as the
476 * window might get destroyed or unmapped in the meanwhile.
478 xwindow_unmanaged_selectinput(win, StructureNotifyMask);
480 if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
481 if(maprq)
482 warn(TR("Window %#x disappeared."), win);
483 goto fail2;
486 /* Is it a dockapp?
488 hints=XGetWMHints(ioncore_g.dpy, win);
490 if(hints!=NULL){
491 if(hints->flags&StateHint)
492 init_state=hints->initial_state;
494 if(!param.dockapp && init_state==WithdrawnState &&
495 hints->flags&IconWindowHint && hints->icon_window!=None){
496 Window icon_win=hints->icon_window;
497 XWindowAttributes icon_attr;
499 if(!XGetWindowAttributes(ioncore_g.dpy, icon_win, &icon_attr)){
500 if(maprq)
501 warn(TR("Window %#x disappeared."), win);
502 XFree((void*)hints);
503 goto fail2;
506 if(!maprq){
507 if(attr.map_state==IsViewable){
508 /* The dockapp might be displaying its "main" window if no
509 * wm that understands dockapps has been managing it.
511 XUnmapWindow(ioncore_g.dpy, win);
512 param.dockapp=TRUE;
513 }else{
514 /* Main window is unmapped on initial scan, but icon window
515 * is mapped. Let's hope it's a dockapp left by e.g. us.
517 if(icon_attr.map_state==IsViewable)
518 param.dockapp=TRUE;
520 }else{
521 param.dockapp=TRUE;
524 if(param.dockapp){
525 char **p=NULL;
526 int n=0;
528 xwindow_unmanaged_selectinput(win, 0);
529 xwindow_unmanaged_selectinput(icon_win, StructureNotifyMask);
531 /* Copy WM_CLASS as _ION_DOCKAPP_HACK */
533 p=xwindow_get_text_property(win, XA_WM_CLASS, &n);
535 if(p!=NULL){
536 xwindow_set_text_property(icon_win, ioncore_g.atom_dockapp_hack,
537 (const char **)p, n);
538 XFreeStringList(p);
539 }else{
540 const char *pdummy[2]={"unknowndockapp", "UnknownDockapp"};
541 xwindow_set_text_property(icon_win, ioncore_g.atom_dockapp_hack,
542 pdummy, 2);
545 win=icon_win;
546 attr=icon_attr;
550 XFree((void*)hints);
553 /* Do we really want to manage it? */
554 if(!param.dockapp && (attr.override_redirect ||
555 (!maprq && attr.map_state!=IsViewable))){
556 goto fail2;
559 attr.width=MAXOF(attr.width, 1);
560 attr.height=MAXOF(attr.height, 1);
562 /* Find root window */
563 FOR_ALL_ROOTWINS(rootwin){
564 if(WROOTWIN_ROOT(rootwin)==attr.root)
565 break;
568 if(rootwin==NULL){
569 warn(TR("Unable to find a matching root window!"));
570 goto fail2;
573 /* Allocate and initialize */
574 cwin=create_clientwin((WWindow*)rootwin, win, &attr);
576 if(cwin==NULL){
577 warn_err();
578 goto fail2;
581 param.geom=REGION_GEOM(cwin);
582 param.maprq=maprq;
583 param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
584 param.switchto=(init_state!=IconicState &&
585 (param.jumpto || clientwin_get_switchto(cwin)));
586 param.gravity=(cwin->size_hints.flags&PWinGravity
587 ? cwin->size_hints.win_gravity
588 : ForgetGravity);
589 param.tfor=clientwin_get_transient_for(cwin);
591 if(!extl_table_gets_b(cwin->proptab, "userpos", &param.userpos))
592 param.userpos=(cwin->size_hints.flags&USPosition);
594 if(cwin->flags&SIZEHINT_PROPS){
595 /* If size hints have been messed with, readjust requested geometry
596 * here. If programs themselves give incompatible geometries and
597 * things don't look good then, it's their fault.
599 region_size_hints_correct((WRegion*)cwin, &param.geom.w, &param.geom.h,
600 FALSE);
603 if(maprq)
604 netwm_check_manage_user_time(cwin, &param);
606 mrshpm[0]=cwin;
607 mrshpm[1]=&param;
609 if(!hook_call_alt(clientwin_do_manage_alt, &mrshpm,
610 (WHookMarshall*)do_manage_mrsh,
611 (WHookMarshallExtl*)do_manage_mrsh_extl)){
612 warn(TR("Unable to manage client window %#x."), win);
613 goto failure;
616 if(ioncore_g.opmode==IONCORE_OPMODE_NORMAL &&
617 !region_is_fully_mapped((WRegion*)cwin) &&
618 !region_skip_focus((WRegion*)cwin)){
619 region_set_activity((WRegion*)cwin, SETPARAM_SET);
622 if(postmanage_check(cwin, &attr)){
623 /* Check for focus_next==NULL does not play nicely with
624 * pointer_focus_hack.
626 /*if(param.jumpto && ioncore_g.focus_next==NULL)*/
627 if(param.jumpto && !region_manager_is_focusnext((WRegion*)cwin))
628 region_goto((WRegion*)cwin);
629 hook_call_o(clientwin_mapped_hook, (Obj*)cwin);
630 return cwin;
633 failure:
634 clientwin_destroyed(cwin);
635 return NULL;
637 fail2:
638 xwindow_unmanaged_selectinput(win, 0);
639 return NULL;
643 void clientwin_tfor_changed(WClientWin *cwin)
645 #if 0
646 WManageParams param=MANAGEPARAMS_INIT;
647 bool succeeded=FALSE;
648 param.tfor=clientwin_get_transient_for(cwin);
649 if(param.tfor==NULL)
650 return;
652 region_rootpos((WRegion*)cwin, &(param.geom.x), &(param.geom.y));
653 param.geom.w=REGION_GEOM(cwin).w;
654 param.geom.h=REGION_GEOM(cwin).h;
655 param.maprq=FALSE;
656 param.userpos=FALSE;
657 param.switchto=region_may_control_focus((WRegion*)cwin);
658 param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
659 param.gravity=ForgetGravity;
661 CALL_ALT_B(succeeded, clientwin_do_manage_alt, (cwin, &param));
662 warn("WM_TRANSIENT_FOR changed for \"%s\".",
663 region_name((WRegion*)cwin));
664 #else
665 warn(TR("Changes is WM_TRANSIENT_FOR property are unsupported."));
666 #endif
670 /*}}}*/
673 /*{{{ Unmanage/destroy */
676 static bool reparent_root(WClientWin *cwin)
678 XWindowAttributes attr;
679 WWindow *par;
680 Window dummy;
681 int x=0, y=0;
683 if(!XGetWindowAttributes(ioncore_g.dpy, cwin->win, &attr))
684 return FALSE;
686 par=REGION_PARENT(cwin);
688 if(par==NULL){
689 x=REGION_GEOM(cwin).x;
690 y=REGION_GEOM(cwin).y;
691 }else{
692 int dr=REGION_GEOM(par).w-REGION_GEOM(cwin).w-REGION_GEOM(cwin).x;
693 int db=REGION_GEOM(par).h-REGION_GEOM(cwin).h-REGION_GEOM(cwin).y;
694 dr=MAXOF(dr, 0);
695 db=MAXOF(db, 0);
697 XTranslateCoordinates(ioncore_g.dpy, par->win, attr.root, 0, 0,
698 &x, &y, &dummy);
700 x-=xgravity_deltax(cwin->size_hints.win_gravity,
701 MAXOF(0, REGION_GEOM(cwin).x), dr);
702 y-=xgravity_deltay(cwin->size_hints.win_gravity,
703 MAXOF(0, REGION_GEOM(cwin).y), db);
706 XReparentWindow(ioncore_g.dpy, cwin->win, attr.root, x, y);
708 return TRUE;
712 void clientwin_deinit(WClientWin *cwin)
714 if(cwin->win!=None){
715 region_pointer_focus_hack(&cwin->region);
717 xwindow_unmanaged_selectinput(cwin->win, 0);
718 XUnmapWindow(ioncore_g.dpy, cwin->win);
720 if(cwin->orig_bw!=0)
721 configure_cwin_bw(cwin->win, cwin->orig_bw);
723 if(reparent_root(cwin)){
724 if(ioncore_g.opmode==IONCORE_OPMODE_DEINIT){
725 XMapWindow(ioncore_g.dpy, cwin->win);
726 /* Make sure the topmost window has focus; it doesn't really
727 * matter which one has as long as some has.
729 xwindow_do_set_focus(cwin->win);
730 }else{
731 set_clientwin_state(cwin, WithdrawnState);
732 netwm_delete_state(cwin);
736 XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
737 XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
740 clientwin_clear_colormaps(cwin);
742 region_deinit((WRegion*)cwin);
747 static bool mrsh_u_c(WHookDummy *fn, void *param)
749 fn(*(Window*)param);
750 return TRUE;
753 static bool mrsh_u_extl(ExtlFn fn, void *param)
755 double d=*(Window*)param;
756 extl_call(fn, "d", NULL, d);
757 return TRUE;
760 static void clientwin_do_unmapped(WClientWin *cwin, Window win)
762 cwin->flags|=CLIENTWIN_UNMAP_RQ;
764 /* First try a graceful chain-dispose */
765 if(!region_rqdispose((WRegion*)cwin)){
766 /* But force dispose anyway */
767 region_dispose((WRegion*)cwin);
770 hook_call(clientwin_unmapped_hook, &win, mrsh_u_c, mrsh_u_extl);
773 /* Used when the window was unmapped */
774 void clientwin_unmapped(WClientWin *cwin)
776 clientwin_do_unmapped(cwin, cwin->win);
780 /* Used when the window was deastroyed */
781 void clientwin_destroyed(WClientWin *cwin)
783 Window win=cwin->win;
784 XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
785 XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
786 xwindow_unmanaged_selectinput(cwin->win, 0);
787 cwin->win=None;
788 clientwin_do_unmapped(cwin, win);
792 /*}}}*/
795 /*{{{ Kill/close */
798 static bool send_clientmsg(Window win, Atom a, Time stmp)
800 XClientMessageEvent ev;
802 ev.type=ClientMessage;
803 ev.window=win;
804 ev.message_type=ioncore_g.atom_wm_protocols;
805 ev.format=32;
806 ev.data.l[0]=a;
807 ev.data.l[1]=stmp;
809 return (XSendEvent(ioncore_g.dpy, win, False, 0L, (XEvent*)&ev)!=0);
813 /*EXTL_DOC
814 * Attempt to kill (with \code{XKillWindow}) the client that owns
815 * the X window correspoding to \var{cwin}.
817 EXTL_EXPORT_MEMBER
818 void clientwin_kill(WClientWin *cwin)
820 XKillClient(ioncore_g.dpy, cwin->win);
824 void clientwin_rqclose(WClientWin *cwin, bool UNUSED(relocate_ignored))
826 /* Ignore relocate parameter -- client windows can always be
827 * destroyed by the application in any case, so way may just as
828 * well assume relocate is always set.
831 if(cwin->flags&CLIENTWIN_P_WM_DELETE){
832 send_clientmsg(cwin->win, ioncore_g.atom_wm_delete,
833 ioncore_get_timestamp());
834 }else{
835 warn(TR("Client does not support the WM_DELETE protocol."));
840 /*}}}*/
843 /*{{{ State (hide/show) */
846 static void set_clientwin_state(WClientWin *cwin, int state)
848 if(cwin->state!=state){
849 cwin->state=state;
850 xwindow_set_state_property(cwin->win, state);
855 static void hide_clientwin(WClientWin *cwin)
857 region_pointer_focus_hack(&cwin->region);
859 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
860 XMoveWindow(ioncore_g.dpy, cwin->win,
861 -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h);
862 return;
865 set_clientwin_state(cwin, IconicState);
866 XSelectInput(ioncore_g.dpy, cwin->win,
867 cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
868 XUnmapWindow(ioncore_g.dpy, cwin->win);
869 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
873 static void show_clientwin(WClientWin *cwin)
875 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
876 XMoveWindow(ioncore_g.dpy, cwin->win,
877 REGION_GEOM(cwin).x, REGION_GEOM(cwin).y);
878 if(cwin->state==NormalState)
879 return;
882 XSelectInput(ioncore_g.dpy, cwin->win,
883 cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
884 XMapWindow(ioncore_g.dpy, cwin->win);
885 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
886 set_clientwin_state(cwin, NormalState);
890 /*}}}*/
893 /*{{{ Resize/reparent/reconf helpers */
896 void clientwin_notify_rootpos(WClientWin *cwin, int rootx, int rooty)
898 XEvent ce;
899 Window win;
901 if(cwin==NULL)
902 return;
904 win=cwin->win;
906 ce.xconfigure.type=ConfigureNotify;
907 ce.xconfigure.event=win;
908 ce.xconfigure.window=win;
909 ce.xconfigure.x=rootx-cwin->orig_bw;
910 ce.xconfigure.y=rooty-cwin->orig_bw;
911 ce.xconfigure.width=REGION_GEOM(cwin).w;
912 ce.xconfigure.height=REGION_GEOM(cwin).h;
913 ce.xconfigure.border_width=cwin->orig_bw;
914 ce.xconfigure.above=None;
915 ce.xconfigure.override_redirect=False;
917 XSelectInput(ioncore_g.dpy, win, cwin->event_mask&~StructureNotifyMask);
918 XSendEvent(ioncore_g.dpy, win, False, StructureNotifyMask, &ce);
919 XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
923 static void sendconfig_clientwin(WClientWin *cwin)
925 int rootx, rooty;
927 region_rootpos(&cwin->region, &rootx, &rooty);
928 clientwin_notify_rootpos(cwin, rootx, rooty);
932 static void do_reparent_clientwin(WClientWin *cwin, Window win, int x, int y)
934 XSelectInput(ioncore_g.dpy, cwin->win,
935 cwin->event_mask&~StructureNotifyMask);
936 XReparentWindow(ioncore_g.dpy, cwin->win, win, x, y);
937 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
941 static void convert_geom(const WFitParams *fp,
942 WClientWin *cwin, WRectangle *geom)
944 WFitParams fptmp=*fp;
945 WSizePolicy szplcy=SIZEPOLICY_FULL_EXACT;
947 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
948 szplcy=cwin->szplcy;*/
950 sizepolicy(&szplcy, (WRegion*)cwin, NULL, REGION_RQGEOM_WEAK_ALL, &fptmp);
952 *geom=fptmp.g;
956 /*}}}*/
959 /*{{{ Region dynfuns */
962 static bool postpone_resize(WClientWin *cwin)
964 return cwin->state == IconicState && cwin->flags&CLIENTWIN_PROP_LAZY_RESIZE;
968 static bool clientwin_fitrep(WClientWin *cwin, WWindow *np,
969 const WFitParams *fp)
971 WRectangle geom;
972 bool changes;
973 int w, h;
975 if(np!=NULL && !region_same_rootwin((WRegion*)cwin, (WRegion*)np))
976 return FALSE;
978 if(fp->mode&REGION_FIT_WHATEVER){
979 geom.x=fp->g.x;
980 geom.y=fp->g.y;
981 geom.w=REGION_GEOM(cwin).w;
982 geom.h=REGION_GEOM(cwin).h;
983 }else{
984 geom=fp->g;
987 changes=(REGION_GEOM(cwin).x!=geom.x ||
988 REGION_GEOM(cwin).y!=geom.y ||
989 REGION_GEOM(cwin).w!=geom.w ||
990 REGION_GEOM(cwin).h!=geom.h);
992 if(np==NULL && !changes)
993 return TRUE;
995 if(np!=NULL){
996 region_unset_parent((WRegion*)cwin);
999 * update netwm properties before mapping, because some apps check the
1000 * netwm state directly when mapped.
1002 * also, update netwm properties after setting the parent, because
1003 * the new state of _NET_WM_STATE_FULLSCREEN is determined based on
1004 * the parent of the cwin.
1006 region_set_parent((WRegion*)cwin, np);
1007 netwm_update_state(cwin);
1009 do_reparent_clientwin(cwin, np->win, geom.x, geom.y);
1010 sendconfig_clientwin(cwin);
1012 if(!REGION_IS_FULLSCREEN(cwin))
1013 cwin->flags&=~CLIENTWIN_FS_RQ;
1016 if (postpone_resize(cwin))
1017 return TRUE;
1019 REGION_GEOM(cwin)=geom;
1021 w=MAXOF(1, geom.w);
1022 h=MAXOF(1, geom.h);
1024 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC && !REGION_IS_MAPPED(cwin)){
1025 XMoveResizeWindow(ioncore_g.dpy, cwin->win,
1026 -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h,
1027 w, h);
1028 }else{
1029 XMoveResizeWindow(ioncore_g.dpy, cwin->win, geom.x, geom.y, w, h);
1032 cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1034 return TRUE;
1038 static void clientwin_map(WClientWin *cwin)
1040 show_clientwin(cwin);
1041 REGION_MARK_MAPPED(cwin);
1045 static void clientwin_unmap(WClientWin *cwin)
1047 hide_clientwin(cwin);
1048 REGION_MARK_UNMAPPED(cwin);
1052 static void clientwin_do_set_focus(WClientWin *cwin, bool warp)
1054 if(cwin->flags&CLIENTWIN_P_WM_TAKE_FOCUS){
1055 Time stmp=ioncore_get_timestamp();
1056 region_finalise_focusing((WRegion*)cwin, cwin->win, warp, stmp, cwin->flags&CLIENTWIN_SET_INPUT);
1057 send_clientmsg(cwin->win, ioncore_g.atom_wm_take_focus, stmp);
1058 }else{
1059 region_finalise_focusing((WRegion*)cwin, cwin->win, warp, CurrentTime, cwin->flags&CLIENTWIN_SET_INPUT);
1062 XSync(ioncore_g.dpy, 0);
1066 void clientwin_restack(WClientWin *cwin, Window other, int mode)
1068 xwindow_restack(cwin->win, other, mode);
1072 void clientwin_stacking(WClientWin *cwin, Window *bottomret, Window *topret)
1074 *bottomret=cwin->win;
1075 *topret=cwin->win;
1079 static Window clientwin_x_window(WClientWin *cwin)
1081 return cwin->win;
1085 static void clientwin_activated(WClientWin *cwin)
1087 clientwin_install_colormap(cwin);
1091 static void clientwin_size_hints(WClientWin *cwin, WSizeHints *hints_ret)
1093 if(cwin->flags&CLIENTWIN_FS_RQ){
1094 /* Do not use size hints, when full screen mode has been
1095 * requested by the client window itself.
1097 sizehints_clear(hints_ret);
1098 }else{
1099 xsizehints_to_sizehints(&cwin->size_hints, hints_ret);
1104 static int clientwin_orientation(WClientWin *cwin)
1106 return (cwin->flags&CLIENTWIN_PROP_O_VERT
1107 ? REGION_ORIENTATION_VERTICAL
1108 : (cwin->flags&CLIENTWIN_PROP_O_HORIZ
1109 ? REGION_ORIENTATION_HORIZONTAL
1110 : REGION_ORIENTATION_NONE));
1114 /*}}}*/
1117 /*{{{ Identity & lookup */
1120 /*EXTL_DOC
1121 * Returns a table containing the properties \code{WM_CLASS} (table entries
1122 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1123 * properties for \var{cwin}. If a property is not set, the corresponding
1124 * field(s) are unset in the table.
1126 EXTL_SAFE
1127 EXTL_EXPORT_MEMBER
1128 ExtlTab clientwin_get_ident(WClientWin *cwin)
1130 char **p=NULL, **p2=NULL, *wrole=NULL;
1131 int n=0, n2=0, n3=0;
1132 Window tforwin=None;
1133 ExtlTab tab;
1134 bool dockapp_hack=FALSE;
1136 p=xwindow_get_text_property(cwin->win, XA_WM_CLASS, &n);
1138 p2=xwindow_get_text_property(cwin->win, ioncore_g.atom_dockapp_hack, &n2);
1140 dockapp_hack=(n2>0);
1142 if(p==NULL){
1143 /* Some dockapps do actually have WM_CLASS, so use it. */
1144 p=p2;
1145 n=n2;
1146 p2=NULL;
1149 wrole=xwindow_get_string_property(cwin->win, ioncore_g.atom_wm_window_role,
1150 &n3);
1152 tab=extl_create_table();
1153 if(n>=2 && p[1]!=NULL)
1154 extl_table_sets_s(tab, "class", p[1]);
1155 if(n>=1 && p[0]!=NULL)
1156 extl_table_sets_s(tab, "instance", p[0]);
1157 if(wrole!=NULL)
1158 extl_table_sets_s(tab, "role", wrole);
1160 if(XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin)
1161 && tforwin!=None){
1162 extl_table_sets_b(tab, "is_transient", TRUE);
1165 if(dockapp_hack)
1166 extl_table_sets_b(tab, "is_dockapp", TRUE);
1168 if(p!=NULL)
1169 XFreeStringList(p);
1170 if(p2!=NULL)
1171 XFreeStringList(p2);
1172 if(wrole!=NULL)
1173 free(wrole);
1175 return tab;
1179 /*}}}*/
1182 /*{{{ ConfigureRequest */
1185 static bool check_fs_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1187 /* check full screen request */
1188 if((ev->value_mask&(CWWidth|CWHeight))==(CWWidth|CWHeight)){
1189 WRegion *grp=region_groupleader_of((WRegion*)cwin);
1190 WScreen *scr=clientwin_fullscreen_chkrq(cwin, ev->width, ev->height);
1192 if(scr!=NULL && REGION_MANAGER(grp)!=(WRegion*)scr){
1193 bool sw=clientwin_fullscreen_may_switchto(cwin);
1195 cwin->flags|=CLIENTWIN_FS_RQ;
1197 if(!region_fullscreen_scr(grp, scr, sw))
1198 cwin->flags&=~CLIENTWIN_FS_RQ;
1200 return TRUE;
1204 return FALSE;
1207 WMPlex* find_mplexer(WRegion *cwin)
1209 if (cwin == NULL)
1210 return NULL;
1211 if(obj_is((Obj*)cwin, &CLASSDESCR(WMPlex)))
1212 return (WMPlex*) cwin;
1213 return find_mplexer(cwin->manager);
1216 /* Returns whether anything was actually changed. */
1217 static bool check_normal_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1219 bool result = FALSE;
1221 if(ev->value_mask&(CWX|CWY|CWWidth|CWHeight)){
1222 WRQGeomParams rq=RQGEOMPARAMS_INIT;
1223 int gdx=0, gdy=0;
1225 rq.flags=REGION_RQGEOM_WEAK_ALL|REGION_RQGEOM_ABSOLUTE;
1227 if(cwin->size_hints.flags&PWinGravity){
1228 rq.flags|=REGION_RQGEOM_GRAVITY;
1229 rq.gravity=cwin->size_hints.win_gravity;
1232 /* Do I need to insert another disparaging comment on the person who
1233 * invented special server-supported window borders that are not
1234 * accounted for in the window size? Keep it simple, stupid!
1236 if(cwin->size_hints.flags&PWinGravity){
1237 gdx=xgravity_deltax(cwin->size_hints.win_gravity,
1238 -cwin->orig_bw, -cwin->orig_bw);
1239 gdy=xgravity_deltay(cwin->size_hints.win_gravity,
1240 -cwin->orig_bw, -cwin->orig_bw);
1243 region_rootpos((WRegion*)cwin, &(rq.geom.x), &(rq.geom.y));
1244 rq.geom.w=REGION_GEOM(cwin).w;
1245 rq.geom.h=REGION_GEOM(cwin).h;
1247 if(ev->value_mask&CWWidth){
1248 /* If x was not changed, keep reference point where it was */
1249 if(cwin->size_hints.flags&PWinGravity){
1250 rq.geom.x+=xgravity_deltax(cwin->size_hints.win_gravity, 0,
1251 ev->width-rq.geom.w);
1253 rq.geom.w=MAXOF(ev->width, 1);
1254 rq.flags&=~REGION_RQGEOM_WEAK_W;
1256 if(ev->value_mask&CWHeight){
1257 /* If y was not changed, keep reference point where it was */
1258 if(cwin->size_hints.flags&PWinGravity){
1259 rq.geom.y+=xgravity_deltay(cwin->size_hints.win_gravity, 0,
1260 ev->height-rq.geom.h);
1262 rq.geom.h=MAXOF(ev->height, 1);
1263 rq.flags&=~REGION_RQGEOM_WEAK_H;
1265 if(ev->value_mask&CWX){
1266 rq.geom.x=ev->x+gdx;
1267 rq.flags&=~REGION_RQGEOM_WEAK_X;
1268 rq.flags&=~REGION_RQGEOM_WEAK_W;
1270 if(ev->value_mask&CWY){
1271 rq.geom.y=ev->y+gdy;
1272 rq.flags&=~REGION_RQGEOM_WEAK_Y;
1273 rq.flags&=~REGION_RQGEOM_WEAK_H;
1276 region_rqgeom((WRegion*)cwin, &rq, NULL);
1278 result = TRUE;
1281 if(ioncore_g.window_stacking_request != IONCORE_WINDOWSTACKINGREQUEST_IGNORE &&
1282 ev->value_mask&CWStackMode){
1283 switch(ev->detail){
1284 case Above:
1285 region_set_activity((WRegion*) cwin, SETPARAM_SET);
1286 /* TODO we should be more conservative here - but what does/should
1287 * region_set_activity return? */
1288 result = TRUE;
1289 break;
1290 case Below:
1291 case TopIf:
1292 case BottomIf:
1293 case Opposite:
1294 /* unimplemented */
1295 break;
1299 return result;
1303 void clientwin_handle_configure_request(WClientWin *cwin,
1304 XConfigureRequestEvent *ev)
1306 if(ev->value_mask&CWBorderWidth)
1307 cwin->orig_bw=ev->border_width;
1309 cwin->flags|=CLIENTWIN_NEED_CFGNTFY;
1311 if(!(cwin->flags&CLIENTWIN_PROP_IGNORE_CFGRQ)){
1312 if(!check_fs_cfgrq(cwin, ev))
1313 check_normal_cfgrq(cwin, ev);
1316 if(cwin->flags&CLIENTWIN_NEED_CFGNTFY){
1317 sendconfig_clientwin(cwin);
1318 cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1323 /*}}}*/
1326 /*{{{ Kludges */
1329 /*EXTL_DOC
1330 * Attempts to fix window size problems with non-ICCCM compliant
1331 * programs.
1333 EXTL_EXPORT_MEMBER
1334 void clientwin_nudge(WClientWin *cwin)
1336 XResizeWindow(ioncore_g.dpy, cwin->win,
1337 2*REGION_GEOM(cwin).w, 2*REGION_GEOM(cwin).h);
1338 XFlush(ioncore_g.dpy);
1339 XResizeWindow(ioncore_g.dpy, cwin->win,
1340 REGION_GEOM(cwin).w, REGION_GEOM(cwin).h);
1344 /*}}}*/
1347 /*{{{ Misc. */
1350 /*EXTL_DOC
1351 * Return the X window id for the client window.
1353 EXTL_SAFE
1354 EXTL_EXPORT_MEMBER
1355 double clientwin_xid(WClientWin *cwin)
1357 return cwin->win;
1361 /*}}}*/
1364 /*{{{ Save/load */
1367 static int last_checkcode=1;
1370 static ExtlTab clientwin_get_configuration(WClientWin *cwin)
1372 int chkc=0;
1373 ExtlTab tab;
1374 SMCfgCallback *cfg_cb;
1375 SMAddCallback *add_cb;
1377 tab=region_get_base_configuration((WRegion*)cwin);
1379 extl_table_sets_d(tab, "windowid", (double)(cwin->win));
1381 if(last_checkcode!=0){
1382 chkc=last_checkcode++;
1383 xwindow_set_integer_property(cwin->win, ioncore_g.atom_checkcode,
1384 chkc);
1385 extl_table_sets_i(tab, "checkcode", chkc);
1388 ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1390 if(cfg_cb!=NULL)
1391 cfg_cb(cwin, tab);
1393 return tab;
1397 static void do_sm(ExtlTab tab)
1399 SMAddCallback *add_cb;
1400 SMCfgCallback *cfg_cb;
1401 WPHolder *ph;
1403 ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1405 if(add_cb!=NULL){
1406 ph=ioncore_get_load_pholder();
1408 if(ph!=NULL){
1409 if(!add_cb(ph, tab))
1410 destroy_obj((Obj*)ph);
1416 WRegion *clientwin_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1418 double wind=0;
1419 Window win=None;
1420 int chkc=0, real_chkc=0;
1421 WClientWin *cwin=NULL;
1422 XWindowAttributes attr;
1423 WRectangle rg;
1424 bool got_chkc=FALSE;
1426 if(!extl_table_gets_d(tab, "windowid", &wind) ||
1427 !extl_table_gets_i(tab, "checkcode", &chkc)){
1428 return NULL;
1431 win=(Window)wind;
1433 if(XWINDOW_REGION_OF(win)!=NULL){
1434 warn("Client window %x already managed.", win);
1435 return NULL;
1438 got_chkc=xwindow_get_integer_property(win, ioncore_g.atom_checkcode,
1439 &real_chkc);
1441 if(!got_chkc || real_chkc!=chkc){
1442 do_sm(tab);
1443 return NULL;
1446 /* Found it! */
1448 if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
1449 warn(TR("Window %#x disappeared."), win);
1450 return NULL;
1453 if(attr.root!=region_root_of((WRegion*)par))
1454 return NULL;
1456 if(attr.override_redirect ||
1457 (ioncore_g.opmode==IONCORE_OPMODE_INIT && attr.map_state!=IsViewable)){
1458 warn(TR("Saved client window does not want to be managed."));
1459 return NULL;
1462 cwin=create_clientwin(par, win, &attr);
1464 if(cwin==NULL)
1465 return FALSE;
1467 /* Reparent and resize taking limits set by size hints into account */
1468 convert_geom(fp, cwin, &rg);
1469 REGION_GEOM(cwin)=rg;
1470 do_reparent_clientwin(cwin, par->win, rg.x, rg.y);
1471 XResizeWindow(ioncore_g.dpy, win, MAXOF(1, rg.w), MAXOF(1, rg.h));
1473 if(!postmanage_check(cwin, &attr)){
1474 clientwin_destroyed(cwin);
1475 return NULL;
1478 return (WRegion*)cwin;
1482 /*}}}*/
1485 /*{{{ Dynfuntab and class info */
1488 static DynFunTab clientwin_dynfuntab[]={
1489 {(DynFun*)region_fitrep,
1490 (DynFun*)clientwin_fitrep},
1492 {region_map,
1493 clientwin_map},
1495 {region_unmap,
1496 clientwin_unmap},
1498 {region_do_set_focus,
1499 clientwin_do_set_focus},
1501 {region_notify_rootpos,
1502 clientwin_notify_rootpos},
1504 {region_restack,
1505 clientwin_restack},
1507 {region_stacking,
1508 clientwin_stacking},
1510 {(DynFun*)region_xwindow,
1511 (DynFun*)clientwin_x_window},
1513 {region_activated,
1514 clientwin_activated},
1516 {region_size_hints,
1517 clientwin_size_hints},
1519 {(DynFun*)region_orientation,
1520 (DynFun*)clientwin_orientation},
1522 {(DynFun*)region_rqclose,
1523 (DynFun*)clientwin_rqclose},
1525 {(DynFun*)region_get_configuration,
1526 (DynFun*)clientwin_get_configuration},
1528 END_DYNFUNTAB
1532 EXTL_EXPORT
1533 IMPLCLASS(WClientWin, WRegion, clientwin_deinit, clientwin_dynfuntab);
1536 /*}}}*/