Revert "link mute/volumeup/volumedown keys to respective amixer commands"
[notion/jeffpc.git] / ioncore / clientwin.c
blob3514912b10dce7f993dc7313faf2346d72f4e972
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 static WSizePolicy get_sizepolicy_winprop(WClientWin *cwin,
82 const char *propname,
83 WSizePolicy value)
85 char *szplcy;
87 if(extl_table_gets_s(cwin->proptab, propname, &szplcy)){
88 string2sizepolicy(szplcy, &value);
89 free(szplcy);
91 return value;
95 #define SIZEHINT_PROPS (CLIENTWIN_PROP_MAXSIZE| \
96 CLIENTWIN_PROP_MINSIZE| \
97 CLIENTWIN_PROP_ASPECT| \
98 CLIENTWIN_PROP_RSZINC| \
99 CLIENTWIN_PROP_I_MAXSIZE| \
100 CLIENTWIN_PROP_I_MINSIZE| \
101 CLIENTWIN_PROP_I_ASPECT| \
102 CLIENTWIN_PROP_I_RSZINC)
105 #define DO_SZH(NAME, FLAG, IFLAG, SZHFLAG, W, H, C) \
106 if(extl_table_is_bool_set(tab, "ignore_" NAME)){ \
107 cwin->flags|=IFLAG; \
108 }else if(extl_table_gets_t(tab, NAME, &tab2)){ \
109 if(extl_table_gets_i(tab2, "w", &i1) && \
110 extl_table_gets_i(tab2, "h", &i2)){ \
111 cwin->size_hints.W=i1; \
112 cwin->size_hints.H=i2; \
114 cwin->size_hints.flags|=SZHFLAG; \
115 cwin->flags|=FLAG; \
117 extl_unref_table(tab2); \
121 static void clientwin_get_winprops(WClientWin *cwin)
123 ExtlTab tab, tab2;
124 char *s;
125 int i1, i2;
127 tab=ioncore_get_winprop(cwin);
129 cwin->proptab=tab;
131 if(tab==extl_table_none())
132 return;
134 if(extl_table_is_bool_set(tab, "transparent"))
135 cwin->flags|=CLIENTWIN_PROP_TRANSPARENT;
137 if(extl_table_is_bool_set(tab, "acrobatic"))
138 cwin->flags|=CLIENTWIN_PROP_ACROBATIC;
140 if(extl_table_is_bool_set(tab, "lazy_resize"))
141 cwin->flags|=CLIENTWIN_PROP_LAZY_RESIZE;
143 DO_SZH("max_size", CLIENTWIN_PROP_MAXSIZE, CLIENTWIN_PROP_I_MAXSIZE,
144 PMaxSize, max_width, max_height, { });
146 DO_SZH("min_size", CLIENTWIN_PROP_MINSIZE, CLIENTWIN_PROP_I_MINSIZE,
147 PMinSize, min_width, min_height, { });
149 DO_SZH("resizeinc", CLIENTWIN_PROP_RSZINC, CLIENTWIN_PROP_I_RSZINC,
150 PResizeInc, width_inc, height_inc, { });
152 DO_SZH("aspect", CLIENTWIN_PROP_ASPECT, CLIENTWIN_PROP_I_ASPECT,
153 PAspect, min_aspect.x, min_aspect.y,
154 { cwin->size_hints.max_aspect.x=i1;
155 cwin->size_hints.max_aspect.y=i2;
158 if(extl_table_is_bool_set(tab, "ignore_cfgrq"))
159 cwin->flags|=CLIENTWIN_PROP_IGNORE_CFGRQ;
161 if(extl_table_gets_s(tab, "orientation", &s)){
162 if(strcmp(s, "vertical")==0)
163 cwin->flags|=CLIENTWIN_PROP_O_VERT;
164 else if(strcmp(s, "horizontal")==0)
165 cwin->flags|=CLIENTWIN_PROP_O_HORIZ;
166 free(s);
171 void clientwin_get_size_hints(WClientWin *cwin)
173 XSizeHints tmp=cwin->size_hints;
175 xwindow_get_sizehints(cwin->win, &(cwin->size_hints));
177 if(cwin->flags&CLIENTWIN_PROP_I_MAXSIZE){
178 cwin->size_hints.flags&=~PMaxSize;
179 }else if(cwin->flags&CLIENTWIN_PROP_MAXSIZE){
180 cwin->size_hints.max_width=tmp.max_width;
181 cwin->size_hints.max_height=tmp.max_height;
182 cwin->size_hints.flags|=PMaxSize;
185 if(cwin->flags&CLIENTWIN_PROP_I_MINSIZE){
186 cwin->size_hints.flags&=~PMinSize;
187 }else if(cwin->flags&CLIENTWIN_PROP_MINSIZE){
188 cwin->size_hints.min_width=tmp.min_width;
189 cwin->size_hints.min_height=tmp.min_height;
190 cwin->size_hints.flags|=PMinSize;
193 if(cwin->flags&CLIENTWIN_PROP_I_ASPECT){
194 cwin->size_hints.flags&=~PAspect;
195 }else if(cwin->flags&CLIENTWIN_PROP_ASPECT){
196 cwin->size_hints.min_aspect=tmp.min_aspect;
197 cwin->size_hints.max_aspect=tmp.max_aspect;
198 cwin->size_hints.flags|=PAspect;
201 if(cwin->flags&CLIENTWIN_PROP_I_RSZINC){
202 cwin->size_hints.flags&=~PResizeInc;
203 }else if(cwin->flags&CLIENTWIN_PROP_RSZINC){
204 cwin->size_hints.width_inc=tmp.width_inc;
205 cwin->size_hints.height_inc=tmp.height_inc;
206 cwin->size_hints.flags|=PResizeInc;
211 void clientwin_get_set_name(WClientWin *cwin)
213 char **list=NULL;
214 int n=0;
216 if(ioncore_g.use_mb)
217 list=netwm_get_name(cwin);
219 if(list==NULL){
220 list=xwindow_get_text_property(cwin->win, XA_WM_NAME, &n);
221 }else{
222 cwin->flags|=CLIENTWIN_USE_NET_WM_NAME;
225 if(list==NULL){
226 /* Special condition kludge: property exists, but couldn't
227 * be converted to a string list.
229 clientwin_set_name(cwin, (n==-1 ? "???" : NULL));
230 }else{
231 clientwin_set_name(cwin, *list);
232 XFreeStringList(list);
237 /* Some standard winprops */
240 bool clientwin_get_switchto(const WClientWin *cwin)
242 bool b;
244 if(ioncore_g.opmode==IONCORE_OPMODE_INIT)
245 return FALSE;
247 if(extl_table_gets_b(cwin->proptab, "switchto", &b))
248 return b;
250 return ioncore_g.switchto_new;
254 int clientwin_get_transient_mode(const WClientWin *cwin)
256 char *s;
257 int mode=TRANSIENT_MODE_NORMAL;
259 if(extl_table_gets_s(cwin->proptab, "transient_mode", &s)){
260 if(strcmp(s, "current")==0)
261 mode=TRANSIENT_MODE_CURRENT;
262 else if(strcmp(s, "off")==0)
263 mode=TRANSIENT_MODE_OFF;
264 free(s);
266 return mode;
270 /*}}}*/
273 /*{{{ Manage/create */
276 static void configure_cwin_bw(Window win, int bw)
278 XWindowChanges wc;
279 ulong wcmask=CWBorderWidth;
281 wc.border_width=bw;
282 XConfigureWindow(ioncore_g.dpy, win, wcmask, &wc);
286 static void set_sane_gravity(Window win)
288 XSetWindowAttributes attr;
290 attr.win_gravity=NorthWestGravity;
292 XChangeWindowAttributes(ioncore_g.dpy, win,
293 CWWinGravity, &attr);
297 static bool clientwin_init(WClientWin *cwin, WWindow *par, Window win,
298 XWindowAttributes *attr)
300 WFitParams fp;
302 cwin->flags=0;
303 cwin->win=win;
304 cwin->state=WithdrawnState;
306 fp.g.x=attr->x;
307 fp.g.y=attr->y;
308 fp.g.w=attr->width;
309 fp.g.h=attr->height;
310 fp.mode=REGION_FIT_EXACT;
312 /* The idiot who invented special server-supported window borders that
313 * are not accounted for in the window size should be "taken behind a
314 * sauna".
316 cwin->orig_bw=attr->border_width;
317 configure_cwin_bw(cwin->win, 0);
318 if(cwin->orig_bw!=0 && cwin->size_hints.flags&PWinGravity){
319 fp.g.x+=xgravity_deltax(cwin->size_hints.win_gravity,
320 -cwin->orig_bw, -cwin->orig_bw);
321 fp.g.y+=xgravity_deltay(cwin->size_hints.win_gravity,
322 -cwin->orig_bw, -cwin->orig_bw);
325 set_sane_gravity(cwin->win);
327 cwin->n_cmapwins=0;
328 cwin->cmap=attr->colormap;
329 cwin->cmaps=NULL;
330 cwin->cmapwins=NULL;
331 cwin->n_cmapwins=0;
332 cwin->event_mask=IONCORE_EVENTMASK_CLIENTWIN;
334 region_init(&(cwin->region), par, &fp);
336 cwin->region.flags|=REGION_GRAB_ON_PARENT;
337 region_add_bindmap(&cwin->region, ioncore_clientwin_bindmap);
339 XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
341 clientwin_register(cwin);
342 clientwin_get_set_name(cwin);
343 clientwin_get_colormaps(cwin);
344 clientwin_get_protocols(cwin);
345 clientwin_get_winprops(cwin);
346 clientwin_get_size_hints(cwin);
348 netwm_update_allowed_actions(cwin);
350 XSaveContext(ioncore_g.dpy, win, ioncore_g.win_context, (XPointer)cwin);
351 XAddToSaveSet(ioncore_g.dpy, win);
353 return TRUE;
357 static WClientWin *create_clientwin(WWindow *par, Window win,
358 XWindowAttributes *attr)
360 CREATEOBJ_IMPL(WClientWin, clientwin, (p, par, win, attr));
365 WClientWin *clientwin_get_transient_for(const WClientWin *cwin)
367 Window tforwin;
368 WClientWin *tfor=NULL;
370 if(clientwin_get_transient_mode(cwin)!=TRANSIENT_MODE_NORMAL)
371 return NULL;
373 if(!XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin))
374 return NULL;
376 if(tforwin==None)
377 return NULL;
379 tfor=XWINDOW_REGION_OF_T(tforwin, WClientWin);
381 if(tfor==cwin){
382 warn(TR("The transient_for hint for \"%s\" points to itself."),
383 region_name((WRegion*)cwin));
384 }else if(tfor==NULL){
385 if(xwindow_region_of(tforwin)!=NULL){
386 warn(TR("Client window \"%s\" has broken transient_for hint. "
387 "(\"Extended WM hints\" multi-parent brain damage?)"),
388 region_name((WRegion*)cwin));
390 }else if(!region_same_rootwin((WRegion*)cwin, (WRegion*)tfor)){
391 warn(TR("The transient_for window for \"%s\" is not on the same "
392 "screen."), region_name((WRegion*)cwin));
393 }else{
394 return tfor;
397 return NULL;
401 static bool postmanage_check(WClientWin *cwin, XWindowAttributes *attr)
403 /* Check that the window exists. The previous check and selectinput
404 * do not seem to catch all cases of window destroyal.
406 XSync(ioncore_g.dpy, False);
408 if(XGetWindowAttributes(ioncore_g.dpy, cwin->win, attr))
409 return TRUE;
411 warn(TR("Window %#x disappeared."), cwin->win);
413 return FALSE;
417 static bool do_manage_mrsh(bool (*fn)(WClientWin *cwin, WManageParams *pm),
418 void **p)
420 return fn((WClientWin*)p[0], (WManageParams*)p[1]);
425 static bool do_manage_mrsh_extl(ExtlFn fn, void **p)
427 WClientWin *cwin=(WClientWin*)p[0];
428 WManageParams *mp=(WManageParams*)p[1];
429 ExtlTab t=manageparams_to_table(mp);
430 bool ret=FALSE;
432 extl_call(fn, "ot", "b", cwin, t, &ret);
434 extl_unref_table(t);
436 return (ret && REGION_MANAGER(cwin)!=NULL);
440 /* This is called when a window is mapped on the root window.
441 * We want to check if we should manage the window and how and
442 * act appropriately.
444 WClientWin* ioncore_manage_clientwin(Window win, bool maprq)
446 WRootWin *rootwin;
447 WClientWin *cwin=NULL;
448 XWindowAttributes attr;
449 XWMHints *hints;
450 int init_state=NormalState;
451 WManageParams param=MANAGEPARAMS_INIT;
452 void *mrshpm[2];
454 param.dockapp=FALSE;
456 /* Is the window already being managed? */
457 cwin=XWINDOW_REGION_OF_T(win, WClientWin);
458 if(cwin!=NULL)
459 return cwin;
461 /* Select for UnmapNotify and DestroyNotify as the
462 * window might get destroyed or unmapped in the meanwhile.
464 xwindow_unmanaged_selectinput(win, StructureNotifyMask);
466 if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
467 if(maprq)
468 warn(TR("Window %#x disappeared."), win);
469 goto fail2;
472 /* Is it a dockapp?
474 hints=XGetWMHints(ioncore_g.dpy, win);
476 if(hints!=NULL){
477 if(hints->flags&StateHint)
478 init_state=hints->initial_state;
480 if(!param.dockapp && init_state==WithdrawnState &&
481 hints->flags&IconWindowHint && hints->icon_window!=None){
482 Window icon_win=hints->icon_window;
483 XWindowAttributes icon_attr;
485 if(!XGetWindowAttributes(ioncore_g.dpy, icon_win, &icon_attr)){
486 if(maprq)
487 warn(TR("Window %#x disappeared."), win);
488 XFree((void*)hints);
489 goto fail2;
492 if(!maprq){
493 if(attr.map_state==IsViewable){
494 /* The dockapp might be displaying its "main" window if no
495 * wm that understands dockapps has been managing it.
497 XUnmapWindow(ioncore_g.dpy, win);
498 param.dockapp=TRUE;
499 }else{
500 /* Main window is unmapped on initial scan, but icon window
501 * is mapped. Let's hope it's a dockapp left by e.g. us.
503 if(icon_attr.map_state==IsViewable)
504 param.dockapp=TRUE;
506 }else{
507 param.dockapp=TRUE;
510 if(param.dockapp){
511 char **p=NULL;
512 int n=0;
514 xwindow_unmanaged_selectinput(win, 0);
515 xwindow_unmanaged_selectinput(icon_win, StructureNotifyMask);
517 /* Copy WM_CLASS as _ION_DOCKAPP_HACK */
519 p=xwindow_get_text_property(win, XA_WM_CLASS, &n);
521 if(p!=NULL){
522 xwindow_set_text_property(icon_win, ioncore_g.atom_dockapp_hack,
523 (const char **)p, n);
524 XFreeStringList(p);
525 }else{
526 const char *pdummy[2]={"unknowndockapp", "UnknownDockapp"};
527 xwindow_set_text_property(icon_win, ioncore_g.atom_dockapp_hack,
528 pdummy, 2);
531 win=icon_win;
532 attr=icon_attr;
536 XFree((void*)hints);
539 /* Do we really want to manage it? */
540 if(!param.dockapp && (attr.override_redirect ||
541 (!maprq && attr.map_state!=IsViewable))){
542 goto fail2;
545 attr.width=maxof(attr.width, 1);
546 attr.height=maxof(attr.height, 1);
548 /* Find root window */
549 FOR_ALL_ROOTWINS(rootwin){
550 if(WROOTWIN_ROOT(rootwin)==attr.root)
551 break;
554 if(rootwin==NULL){
555 warn(TR("Unable to find a matching root window!"));
556 goto fail2;
559 /* Allocate and initialize */
560 cwin=create_clientwin((WWindow*)rootwin, win, &attr);
562 if(cwin==NULL){
563 warn_err();
564 goto fail2;
567 param.geom=REGION_GEOM(cwin);
568 param.maprq=maprq;
569 param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
570 param.switchto=(init_state!=IconicState &&
571 (param.jumpto || clientwin_get_switchto(cwin)));
572 param.gravity=(cwin->size_hints.flags&PWinGravity
573 ? cwin->size_hints.win_gravity
574 : ForgetGravity);
575 param.tfor=clientwin_get_transient_for(cwin);
577 if(!extl_table_gets_b(cwin->proptab, "userpos", &param.userpos))
578 param.userpos=(cwin->size_hints.flags&USPosition);
580 if(cwin->flags&SIZEHINT_PROPS){
581 /* If size hints have been messed with, readjust requested geometry
582 * here. If programs themselves give incompatible geometries and
583 * things don't look good then, it's their fault.
585 region_size_hints_correct((WRegion*)cwin, &param.geom.w, &param.geom.h,
586 FALSE);
589 if(maprq)
590 netwm_check_manage_user_time(cwin, &param);
592 mrshpm[0]=cwin;
593 mrshpm[1]=&param;
595 if(!hook_call_alt(clientwin_do_manage_alt, &mrshpm,
596 (WHookMarshall*)do_manage_mrsh,
597 (WHookMarshallExtl*)do_manage_mrsh_extl)){
598 warn(TR("Unable to manage client window %#x."), win);
599 goto failure;
602 if(ioncore_g.opmode==IONCORE_OPMODE_NORMAL &&
603 !region_is_fully_mapped((WRegion*)cwin) &&
604 !region_skip_focus((WRegion*)cwin)){
605 region_set_activity((WRegion*)cwin, SETPARAM_SET);
608 if(postmanage_check(cwin, &attr)){
609 /* Check for focus_next==NULL does not play nicely with
610 * pointer_focus_hack.
612 /*if(param.jumpto && ioncore_g.focus_next==NULL)*/
613 if(param.jumpto && !region_manager_is_focusnext((WRegion*)cwin))
614 region_goto((WRegion*)cwin);
615 hook_call_o(clientwin_mapped_hook, (Obj*)cwin);
616 return cwin;
619 failure:
620 clientwin_destroyed(cwin);
621 return NULL;
623 fail2:
624 xwindow_unmanaged_selectinput(win, 0);
625 return NULL;
629 void clientwin_tfor_changed(WClientWin *cwin)
631 #if 0
632 WManageParams param=MANAGEPARAMS_INIT;
633 bool succeeded=FALSE;
634 param.tfor=clientwin_get_transient_for(cwin);
635 if(param.tfor==NULL)
636 return;
638 region_rootpos((WRegion*)cwin, &(param.geom.x), &(param.geom.y));
639 param.geom.w=REGION_GEOM(cwin).w;
640 param.geom.h=REGION_GEOM(cwin).h;
641 param.maprq=FALSE;
642 param.userpos=FALSE;
643 param.switchto=region_may_control_focus((WRegion*)cwin);
644 param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
645 param.gravity=ForgetGravity;
647 CALL_ALT_B(succeeded, clientwin_do_manage_alt, (cwin, &param));
648 warn("WM_TRANSIENT_FOR changed for \"%s\".",
649 region_name((WRegion*)cwin));
650 #else
651 warn(TR("Changes is WM_TRANSIENT_FOR property are unsupported."));
652 #endif
656 /*}}}*/
659 /*{{{ Unmanage/destroy */
662 static bool reparent_root(WClientWin *cwin)
664 XWindowAttributes attr;
665 WWindow *par;
666 Window dummy;
667 int x=0, y=0;
669 if(!XGetWindowAttributes(ioncore_g.dpy, cwin->win, &attr))
670 return FALSE;
672 par=REGION_PARENT(cwin);
674 if(par==NULL){
675 x=REGION_GEOM(cwin).x;
676 y=REGION_GEOM(cwin).y;
677 }else{
678 int dr=REGION_GEOM(par).w-REGION_GEOM(cwin).w-REGION_GEOM(cwin).x;
679 int db=REGION_GEOM(par).h-REGION_GEOM(cwin).h-REGION_GEOM(cwin).y;
680 dr=maxof(dr, 0);
681 db=maxof(db, 0);
683 XTranslateCoordinates(ioncore_g.dpy, par->win, attr.root, 0, 0,
684 &x, &y, &dummy);
686 x-=xgravity_deltax(cwin->size_hints.win_gravity,
687 maxof(0, REGION_GEOM(cwin).x), dr);
688 y-=xgravity_deltay(cwin->size_hints.win_gravity,
689 maxof(0, REGION_GEOM(cwin).y), db);
692 XReparentWindow(ioncore_g.dpy, cwin->win, attr.root, x, y);
694 return TRUE;
698 void clientwin_deinit(WClientWin *cwin)
700 WRegion *reg;
702 if(cwin->win!=None){
703 region_pointer_focus_hack(&cwin->region);
705 xwindow_unmanaged_selectinput(cwin->win, 0);
706 XUnmapWindow(ioncore_g.dpy, cwin->win);
708 if(cwin->orig_bw!=0)
709 configure_cwin_bw(cwin->win, cwin->orig_bw);
711 if(reparent_root(cwin)){
712 if(ioncore_g.opmode==IONCORE_OPMODE_DEINIT){
713 XMapWindow(ioncore_g.dpy, cwin->win);
714 /* Make sure the topmost window has focus; it doesn't really
715 * matter which one has as long as some has.
717 xwindow_do_set_focus(cwin->win);
718 }else{
719 set_clientwin_state(cwin, WithdrawnState);
720 netwm_delete_state(cwin);
724 XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
725 XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
728 clientwin_clear_colormaps(cwin);
730 region_deinit((WRegion*)cwin);
735 static bool mrsh_u_c(WHookDummy *fn, void *param)
737 fn(*(Window*)param);
738 return TRUE;
741 static bool mrsh_u_extl(ExtlFn fn, void *param)
743 double d=*(Window*)param;
744 extl_call(fn, "d", NULL, d);
745 return TRUE;
748 static void clientwin_do_unmapped(WClientWin *cwin, Window win)
750 cwin->flags|=CLIENTWIN_UNMAP_RQ;
752 /* First try a graceful chain-dispose */
753 if(!region_rqdispose((WRegion*)cwin)){
754 /* But force dispose anyway */
755 region_dispose((WRegion*)cwin);
758 hook_call(clientwin_unmapped_hook, &win, mrsh_u_c, mrsh_u_extl);
761 /* Used when the window was unmapped */
762 void clientwin_unmapped(WClientWin *cwin)
764 clientwin_do_unmapped(cwin, cwin->win);
768 /* Used when the window was deastroyed */
769 void clientwin_destroyed(WClientWin *cwin)
771 Window win=cwin->win;
772 XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
773 XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
774 xwindow_unmanaged_selectinput(cwin->win, 0);
775 cwin->win=None;
776 clientwin_do_unmapped(cwin, win);
780 /*}}}*/
783 /*{{{ Kill/close */
786 static bool send_clientmsg(Window win, Atom a, Time stmp)
788 XClientMessageEvent ev;
790 ev.type=ClientMessage;
791 ev.window=win;
792 ev.message_type=ioncore_g.atom_wm_protocols;
793 ev.format=32;
794 ev.data.l[0]=a;
795 ev.data.l[1]=stmp;
797 return (XSendEvent(ioncore_g.dpy, win, False, 0L, (XEvent*)&ev)!=0);
801 /*EXTL_DOC
802 * Attempt to kill (with \code{XKillWindow}) the client that owns
803 * the X window correspoding to \var{cwin}.
805 EXTL_EXPORT_MEMBER
806 void clientwin_kill(WClientWin *cwin)
808 XKillClient(ioncore_g.dpy, cwin->win);
812 void clientwin_rqclose(WClientWin *cwin, bool relocate_ignored)
814 /* Ignore relocate parameter -- client windows can always be
815 * destroyed by the application in any case, so way may just as
816 * well assume relocate is always set.
819 if(cwin->flags&CLIENTWIN_P_WM_DELETE){
820 send_clientmsg(cwin->win, ioncore_g.atom_wm_delete,
821 ioncore_get_timestamp());
822 }else{
823 warn(TR("Client does not support the WM_DELETE protocol."));
828 /*}}}*/
831 /*{{{ State (hide/show) */
834 static void set_clientwin_state(WClientWin *cwin, int state)
836 if(cwin->state!=state){
837 cwin->state=state;
838 xwindow_set_state_property(cwin->win, state);
843 static void hide_clientwin(WClientWin *cwin)
845 region_pointer_focus_hack(&cwin->region);
847 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
848 XMoveWindow(ioncore_g.dpy, cwin->win,
849 -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h);
850 return;
853 set_clientwin_state(cwin, IconicState);
854 XSelectInput(ioncore_g.dpy, cwin->win,
855 cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
856 XUnmapWindow(ioncore_g.dpy, cwin->win);
857 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
861 static void show_clientwin(WClientWin *cwin)
863 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
864 XMoveWindow(ioncore_g.dpy, cwin->win,
865 REGION_GEOM(cwin).x, REGION_GEOM(cwin).y);
866 if(cwin->state==NormalState)
867 return;
870 XSelectInput(ioncore_g.dpy, cwin->win,
871 cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
872 XMapWindow(ioncore_g.dpy, cwin->win);
873 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
874 set_clientwin_state(cwin, NormalState);
878 /*}}}*/
881 /*{{{ Resize/reparent/reconf helpers */
884 void clientwin_notify_rootpos(WClientWin *cwin, int rootx, int rooty)
886 XEvent ce;
887 Window win;
889 if(cwin==NULL)
890 return;
892 win=cwin->win;
894 ce.xconfigure.type=ConfigureNotify;
895 ce.xconfigure.event=win;
896 ce.xconfigure.window=win;
897 ce.xconfigure.x=rootx-cwin->orig_bw;
898 ce.xconfigure.y=rooty-cwin->orig_bw;
899 ce.xconfigure.width=REGION_GEOM(cwin).w;
900 ce.xconfigure.height=REGION_GEOM(cwin).h;
901 ce.xconfigure.border_width=cwin->orig_bw;
902 ce.xconfigure.above=None;
903 ce.xconfigure.override_redirect=False;
905 XSelectInput(ioncore_g.dpy, win, cwin->event_mask&~StructureNotifyMask);
906 XSendEvent(ioncore_g.dpy, win, False, StructureNotifyMask, &ce);
907 XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
911 static void sendconfig_clientwin(WClientWin *cwin)
913 int rootx, rooty;
915 region_rootpos(&cwin->region, &rootx, &rooty);
916 clientwin_notify_rootpos(cwin, rootx, rooty);
920 static void do_reparent_clientwin(WClientWin *cwin, Window win, int x, int y)
922 XSelectInput(ioncore_g.dpy, cwin->win,
923 cwin->event_mask&~StructureNotifyMask);
924 XReparentWindow(ioncore_g.dpy, cwin->win, win, x, y);
925 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
929 static void convert_geom(const WFitParams *fp,
930 WClientWin *cwin, WRectangle *geom)
932 WFitParams fptmp=*fp;
933 WSizePolicy szplcy=SIZEPOLICY_FULL_EXACT;
935 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
936 szplcy=cwin->szplcy;*/
938 sizepolicy(&szplcy, (WRegion*)cwin, NULL, REGION_RQGEOM_WEAK_ALL, &fptmp);
940 *geom=fptmp.g;
944 /*}}}*/
947 /*{{{ Region dynfuns */
950 static bool postpone_resize(WClientWin *cwin)
952 return cwin->state == IconicState && cwin->flags&CLIENTWIN_PROP_LAZY_RESIZE;
956 static bool clientwin_fitrep(WClientWin *cwin, WWindow *np,
957 const WFitParams *fp)
959 WRectangle geom;
960 bool changes;
961 int w, h;
963 if(np!=NULL && !region_same_rootwin((WRegion*)cwin, (WRegion*)np))
964 return FALSE;
966 if(fp->mode&REGION_FIT_WHATEVER){
967 geom.x=fp->g.x;
968 geom.y=fp->g.y;
969 geom.w=REGION_GEOM(cwin).w;
970 geom.h=REGION_GEOM(cwin).h;
971 }else{
972 geom=fp->g;
975 changes=(REGION_GEOM(cwin).x!=geom.x ||
976 REGION_GEOM(cwin).y!=geom.y ||
977 REGION_GEOM(cwin).w!=geom.w ||
978 REGION_GEOM(cwin).h!=geom.h);
980 if(np==NULL && !changes)
981 return TRUE;
983 if(np!=NULL){
984 region_unset_parent((WRegion*)cwin);
987 * update netwm properties before mapping, because some apps check the
988 * netwm state directly when mapped.
990 * also, update netwm properties after setting the parent, because
991 * the new state of _NET_WM_STATE_FULLSCREEN is determined based on
992 * the parent of the cwin.
994 region_set_parent((WRegion*)cwin, np);
995 netwm_update_state(cwin);
997 do_reparent_clientwin(cwin, np->win, geom.x, geom.y);
998 sendconfig_clientwin(cwin);
1000 if(!REGION_IS_FULLSCREEN(cwin))
1001 cwin->flags&=~CLIENTWIN_FS_RQ;
1004 if (postpone_resize(cwin))
1005 return TRUE;
1007 REGION_GEOM(cwin)=geom;
1009 w=maxof(1, geom.w);
1010 h=maxof(1, geom.h);
1012 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC && !REGION_IS_MAPPED(cwin)){
1013 XMoveResizeWindow(ioncore_g.dpy, cwin->win,
1014 -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h,
1015 w, h);
1016 }else{
1017 XMoveResizeWindow(ioncore_g.dpy, cwin->win, geom.x, geom.y, w, h);
1020 cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1022 return TRUE;
1026 static void clientwin_map(WClientWin *cwin)
1028 show_clientwin(cwin);
1029 REGION_MARK_MAPPED(cwin);
1033 static void clientwin_unmap(WClientWin *cwin)
1035 hide_clientwin(cwin);
1036 REGION_MARK_UNMAPPED(cwin);
1040 static void clientwin_do_set_focus(WClientWin *cwin, bool warp)
1042 if(cwin->flags&CLIENTWIN_P_WM_TAKE_FOCUS){
1043 Time stmp=ioncore_get_timestamp();
1044 region_finalise_focusing((WRegion*)cwin, cwin->win, warp, stmp);
1045 send_clientmsg(cwin->win, ioncore_g.atom_wm_take_focus, stmp);
1046 }else{
1047 region_finalise_focusing((WRegion*)cwin, cwin->win, warp, CurrentTime);
1050 XSync(ioncore_g.dpy, 0);
1054 void clientwin_restack(WClientWin *cwin, Window other, int mode)
1056 xwindow_restack(cwin->win, other, mode);
1060 void clientwin_stacking(WClientWin *cwin, Window *bottomret, Window *topret)
1062 *bottomret=cwin->win;
1063 *topret=cwin->win;
1067 static Window clientwin_x_window(WClientWin *cwin)
1069 return cwin->win;
1073 static void clientwin_activated(WClientWin *cwin)
1075 clientwin_install_colormap(cwin);
1079 static void clientwin_size_hints(WClientWin *cwin, WSizeHints *hints_ret)
1081 if(cwin->flags&CLIENTWIN_FS_RQ){
1082 /* Do not use size hints, when full screen mode has been
1083 * requested by the client window itself.
1085 sizehints_clear(hints_ret);
1086 }else{
1087 xsizehints_to_sizehints(&cwin->size_hints, hints_ret);
1092 static int clientwin_orientation(WClientWin *cwin)
1094 return (cwin->flags&CLIENTWIN_PROP_O_VERT
1095 ? REGION_ORIENTATION_VERTICAL
1096 : (cwin->flags&CLIENTWIN_PROP_O_HORIZ
1097 ? REGION_ORIENTATION_HORIZONTAL
1098 : REGION_ORIENTATION_NONE));
1102 /*}}}*/
1105 /*{{{ Identity & lookup */
1108 /*EXTL_DOC
1109 * Returns a table containing the properties \code{WM_CLASS} (table entries
1110 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1111 * properties for \var{cwin}. If a property is not set, the corresponding
1112 * field(s) are unset in the table.
1114 EXTL_SAFE
1115 EXTL_EXPORT_MEMBER
1116 ExtlTab clientwin_get_ident(WClientWin *cwin)
1118 char **p=NULL, **p2=NULL, *wrole=NULL;
1119 int n=0, n2=0, n3=0, tmp=0;
1120 Window tforwin=None;
1121 ExtlTab tab;
1122 bool dockapp_hack=FALSE;
1124 p=xwindow_get_text_property(cwin->win, XA_WM_CLASS, &n);
1126 p2=xwindow_get_text_property(cwin->win, ioncore_g.atom_dockapp_hack, &n2);
1128 dockapp_hack=(n2>0);
1130 if(p==NULL){
1131 /* Some dockapps do actually have WM_CLASS, so use it. */
1132 p=p2;
1133 n=n2;
1134 p2=NULL;
1137 wrole=xwindow_get_string_property(cwin->win, ioncore_g.atom_wm_window_role,
1138 &n3);
1140 tab=extl_create_table();
1141 if(n>=2 && p[1]!=NULL)
1142 extl_table_sets_s(tab, "class", p[1]);
1143 if(n>=1 && p[0]!=NULL)
1144 extl_table_sets_s(tab, "instance", p[0]);
1145 if(wrole!=NULL)
1146 extl_table_sets_s(tab, "role", wrole);
1148 if(XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin)
1149 && tforwin!=None){
1150 extl_table_sets_b(tab, "is_transient", TRUE);
1153 if(dockapp_hack)
1154 extl_table_sets_b(tab, "is_dockapp", TRUE);
1156 if(p!=NULL)
1157 XFreeStringList(p);
1158 if(p2!=NULL)
1159 XFreeStringList(p2);
1160 if(wrole!=NULL)
1161 free(wrole);
1163 return tab;
1167 /*}}}*/
1170 /*{{{ ConfigureRequest */
1173 static bool check_fs_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1175 /* check full screen request */
1176 if((ev->value_mask&(CWWidth|CWHeight))==(CWWidth|CWHeight)){
1177 WRegion *grp=region_groupleader_of((WRegion*)cwin);
1178 WScreen *scr=clientwin_fullscreen_chkrq(cwin, ev->width, ev->height);
1180 if(scr!=NULL && REGION_MANAGER(grp)!=(WRegion*)scr){
1181 bool sw=clientwin_fullscreen_may_switchto(cwin);
1183 cwin->flags|=CLIENTWIN_FS_RQ;
1185 if(!region_fullscreen_scr(grp, scr, sw))
1186 cwin->flags&=~CLIENTWIN_FS_RQ;
1188 return TRUE;
1192 return FALSE;
1195 WMPlex* find_mplexer(WRegion *cwin)
1197 if (cwin == NULL)
1198 return NULL;
1199 if(obj_is((Obj*)cwin, &CLASSDESCR(WMPlex)))
1200 return (WMPlex*) cwin;
1201 return find_mplexer(cwin->manager);
1204 /* Returns whether anything was actually changed. */
1205 static bool check_normal_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1207 bool result = FALSE;
1209 if(ev->value_mask&(CWX|CWY|CWWidth|CWHeight)){
1210 WRQGeomParams rq=RQGEOMPARAMS_INIT;
1211 int gdx=0, gdy=0;
1213 rq.flags=REGION_RQGEOM_WEAK_ALL|REGION_RQGEOM_ABSOLUTE;
1215 if(cwin->size_hints.flags&PWinGravity){
1216 rq.flags|=REGION_RQGEOM_GRAVITY;
1217 rq.gravity=cwin->size_hints.win_gravity;
1220 /* Do I need to insert another disparaging comment on the person who
1221 * invented special server-supported window borders that are not
1222 * accounted for in the window size? Keep it simple, stupid!
1224 if(cwin->size_hints.flags&PWinGravity){
1225 gdx=xgravity_deltax(cwin->size_hints.win_gravity,
1226 -cwin->orig_bw, -cwin->orig_bw);
1227 gdy=xgravity_deltay(cwin->size_hints.win_gravity,
1228 -cwin->orig_bw, -cwin->orig_bw);
1231 region_rootpos((WRegion*)cwin, &(rq.geom.x), &(rq.geom.y));
1232 rq.geom.w=REGION_GEOM(cwin).w;
1233 rq.geom.h=REGION_GEOM(cwin).h;
1235 if(ev->value_mask&CWWidth){
1236 /* If x was not changed, keep reference point where it was */
1237 if(cwin->size_hints.flags&PWinGravity){
1238 rq.geom.x+=xgravity_deltax(cwin->size_hints.win_gravity, 0,
1239 ev->width-rq.geom.w);
1241 rq.geom.w=maxof(ev->width, 1);
1242 rq.flags&=~REGION_RQGEOM_WEAK_W;
1244 if(ev->value_mask&CWHeight){
1245 /* If y was not changed, keep reference point where it was */
1246 if(cwin->size_hints.flags&PWinGravity){
1247 rq.geom.y+=xgravity_deltay(cwin->size_hints.win_gravity, 0,
1248 ev->height-rq.geom.h);
1250 rq.geom.h=maxof(ev->height, 1);
1251 rq.flags&=~REGION_RQGEOM_WEAK_H;
1253 if(ev->value_mask&CWX){
1254 rq.geom.x=ev->x+gdx;
1255 rq.flags&=~REGION_RQGEOM_WEAK_X;
1256 rq.flags&=~REGION_RQGEOM_WEAK_W;
1258 if(ev->value_mask&CWY){
1259 rq.geom.y=ev->y+gdy;
1260 rq.flags&=~REGION_RQGEOM_WEAK_Y;
1261 rq.flags&=~REGION_RQGEOM_WEAK_H;
1264 region_rqgeom((WRegion*)cwin, &rq, NULL);
1266 result = TRUE;
1269 if(ev->value_mask&CWStackMode){
1270 switch(ev->detail){
1271 case Above:
1272 region_set_activity((WRegion*) cwin, SETPARAM_SET);
1273 /* TODO we should be more conservative here - but what does/should
1274 * region_set_activity return? */
1275 result = TRUE;
1276 break;
1277 case Below:
1278 case TopIf:
1279 case BottomIf:
1280 case Opposite:
1281 /* unimplemented */
1282 break;
1286 return result;
1290 void clientwin_handle_configure_request(WClientWin *cwin,
1291 XConfigureRequestEvent *ev)
1293 if(ev->value_mask&CWBorderWidth)
1294 cwin->orig_bw=ev->border_width;
1296 cwin->flags|=CLIENTWIN_NEED_CFGNTFY;
1298 if(!(cwin->flags&CLIENTWIN_PROP_IGNORE_CFGRQ)){
1299 if(!check_fs_cfgrq(cwin, ev))
1300 check_normal_cfgrq(cwin, ev);
1303 if(cwin->flags&CLIENTWIN_NEED_CFGNTFY){
1304 sendconfig_clientwin(cwin);
1305 cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1310 /*}}}*/
1313 /*{{{ Kludges */
1316 /*EXTL_DOC
1317 * Attempts to fix window size problems with non-ICCCM compliant
1318 * programs.
1320 EXTL_EXPORT_MEMBER
1321 void clientwin_nudge(WClientWin *cwin)
1323 XResizeWindow(ioncore_g.dpy, cwin->win,
1324 2*REGION_GEOM(cwin).w, 2*REGION_GEOM(cwin).h);
1325 XFlush(ioncore_g.dpy);
1326 XResizeWindow(ioncore_g.dpy, cwin->win,
1327 REGION_GEOM(cwin).w, REGION_GEOM(cwin).h);
1331 /*}}}*/
1334 /*{{{ Misc. */
1337 /*EXTL_DOC
1338 * Return the X window id for the client window.
1340 EXTL_SAFE
1341 EXTL_EXPORT_MEMBER
1342 double clientwin_xid(WClientWin *cwin)
1344 return cwin->win;
1348 /*}}}*/
1351 /*{{{ Save/load */
1354 static int last_checkcode=1;
1357 static ExtlTab clientwin_get_configuration(WClientWin *cwin)
1359 int chkc=0;
1360 ExtlTab tab;
1361 SMCfgCallback *cfg_cb;
1362 SMAddCallback *add_cb;
1364 tab=region_get_base_configuration((WRegion*)cwin);
1366 extl_table_sets_d(tab, "windowid", (double)(cwin->win));
1368 if(last_checkcode!=0){
1369 chkc=last_checkcode++;
1370 xwindow_set_integer_property(cwin->win, ioncore_g.atom_checkcode,
1371 chkc);
1372 extl_table_sets_i(tab, "checkcode", chkc);
1375 ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1377 if(cfg_cb!=NULL)
1378 cfg_cb(cwin, tab);
1380 return tab;
1384 static void do_sm(ExtlTab tab)
1386 SMAddCallback *add_cb;
1387 SMCfgCallback *cfg_cb;
1388 WPHolder *ph;
1390 ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1392 if(add_cb!=NULL){
1393 ph=ioncore_get_load_pholder();
1395 if(ph!=NULL){
1396 if(!add_cb(ph, tab))
1397 destroy_obj((Obj*)ph);
1403 WRegion *clientwin_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1405 double wind=0;
1406 Window win=None;
1407 int chkc=0, real_chkc=0;
1408 WClientWin *cwin=NULL;
1409 XWindowAttributes attr;
1410 WRectangle rg;
1411 bool got_chkc=FALSE;
1413 if(!extl_table_gets_d(tab, "windowid", &wind) ||
1414 !extl_table_gets_i(tab, "checkcode", &chkc)){
1415 return NULL;
1418 win=(Window)wind;
1420 if(XWINDOW_REGION_OF(win)!=NULL){
1421 warn("Client window %x already managed.", win);
1422 return NULL;
1425 got_chkc=xwindow_get_integer_property(win, ioncore_g.atom_checkcode,
1426 &real_chkc);
1428 if(!got_chkc || real_chkc!=chkc){
1429 do_sm(tab);
1430 return NULL;
1433 /* Found it! */
1435 if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
1436 warn(TR("Window %#x disappeared."), win);
1437 return NULL;
1440 if(attr.root!=region_root_of((WRegion*)par))
1441 return NULL;
1443 if(attr.override_redirect ||
1444 (ioncore_g.opmode==IONCORE_OPMODE_INIT && attr.map_state!=IsViewable)){
1445 warn(TR("Saved client window does not want to be managed."));
1446 return NULL;
1449 cwin=create_clientwin(par, win, &attr);
1451 if(cwin==NULL)
1452 return FALSE;
1454 /* Reparent and resize taking limits set by size hints into account */
1455 convert_geom(fp, cwin, &rg);
1456 REGION_GEOM(cwin)=rg;
1457 do_reparent_clientwin(cwin, par->win, rg.x, rg.y);
1458 XResizeWindow(ioncore_g.dpy, win, maxof(1, rg.w), maxof(1, rg.h));
1460 if(!postmanage_check(cwin, &attr)){
1461 clientwin_destroyed(cwin);
1462 return NULL;
1465 return (WRegion*)cwin;
1469 /*}}}*/
1472 /*{{{ Dynfuntab and class info */
1475 static DynFunTab clientwin_dynfuntab[]={
1476 {(DynFun*)region_fitrep,
1477 (DynFun*)clientwin_fitrep},
1479 {region_map,
1480 clientwin_map},
1482 {region_unmap,
1483 clientwin_unmap},
1485 {region_do_set_focus,
1486 clientwin_do_set_focus},
1488 {region_notify_rootpos,
1489 clientwin_notify_rootpos},
1491 {region_restack,
1492 clientwin_restack},
1494 {region_stacking,
1495 clientwin_stacking},
1497 {(DynFun*)region_xwindow,
1498 (DynFun*)clientwin_x_window},
1500 {region_activated,
1501 clientwin_activated},
1503 {region_size_hints,
1504 clientwin_size_hints},
1506 {(DynFun*)region_orientation,
1507 (DynFun*)clientwin_orientation},
1509 {(DynFun*)region_rqclose,
1510 (DynFun*)clientwin_rqclose},
1512 {(DynFun*)region_get_configuration,
1513 (DynFun*)clientwin_get_configuration},
1515 END_DYNFUNTAB
1519 EXTL_EXPORT
1520 IMPLCLASS(WClientWin, WRegion, clientwin_deinit, clientwin_dynfuntab);
1523 /*}}}*/