2 * ion/ioncore/clientwin.c
4 * Copyright (c) Tuomo Valkonen 1999-2008.
6 * See the included file LICENSE for details.
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>
24 #include "clientwin.h"
34 #include "fullscreen.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;
64 cwin->flags&=~(CLIENTWIN_P_WM_DELETE|CLIENTWIN_P_WM_TAKE_FOCUS);
66 if(!XGetWMProtocols(ioncore_g.dpy, cwin->win, &protocols, &n))
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;
77 XFree((char*)protocols);
81 static WSizePolicy get_sizepolicy_winprop(WClientWin *cwin,
87 if(extl_table_gets_s(cwin->proptab, propname, &szplcy)){
88 string2sizepolicy(szplcy, &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; \
117 extl_unref_table(tab2); \
121 static void clientwin_get_winprops(WClientWin *cwin)
127 tab=ioncore_get_winprop(cwin);
131 if(tab==extl_table_none())
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 DO_SZH("max_size", CLIENTWIN_PROP_MAXSIZE, CLIENTWIN_PROP_I_MAXSIZE,
141 PMaxSize, max_width, max_height, );
143 DO_SZH("min_size", CLIENTWIN_PROP_MINSIZE, CLIENTWIN_PROP_I_MINSIZE,
144 PMinSize, min_width, min_height, );
146 DO_SZH("resizeinc", CLIENTWIN_PROP_RSZINC, CLIENTWIN_PROP_I_RSZINC,
147 PResizeInc, width_inc, height_inc, );
149 DO_SZH("aspect", CLIENTWIN_PROP_ASPECT, CLIENTWIN_PROP_I_ASPECT,
150 PAspect, min_aspect.x, min_aspect.y,
151 { cwin->size_hints.max_aspect.x=i1;
152 cwin->size_hints.max_aspect.y=i2;
155 if(extl_table_is_bool_set(tab, "ignore_cfgrq"))
156 cwin->flags|=CLIENTWIN_PROP_IGNORE_CFGRQ;
158 if(extl_table_gets_s(tab, "orientation", &s)){
159 if(strcmp(s, "vertical")==0)
160 cwin->flags|=CLIENTWIN_PROP_O_VERT;
161 else if(strcmp(s, "horizontal")==0)
162 cwin->flags|=CLIENTWIN_PROP_O_HORIZ;
168 void clientwin_get_size_hints(WClientWin *cwin)
170 XSizeHints tmp=cwin->size_hints;
172 xwindow_get_sizehints(cwin->win, &(cwin->size_hints));
174 if(cwin->flags&CLIENTWIN_PROP_I_MAXSIZE){
175 cwin->size_hints.flags&=~PMaxSize;
176 }else if(cwin->flags&CLIENTWIN_PROP_MAXSIZE){
177 cwin->size_hints.max_width=tmp.max_width;
178 cwin->size_hints.max_height=tmp.max_height;
179 cwin->size_hints.flags|=PMaxSize;
182 if(cwin->flags&CLIENTWIN_PROP_I_MINSIZE){
183 cwin->size_hints.flags&=~PMinSize;
184 }else 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;
190 if(cwin->flags&CLIENTWIN_PROP_I_ASPECT){
191 cwin->size_hints.flags&=~PAspect;
192 }else if(cwin->flags&CLIENTWIN_PROP_ASPECT){
193 cwin->size_hints.min_aspect=tmp.min_aspect;
194 cwin->size_hints.max_aspect=tmp.max_aspect;
195 cwin->size_hints.flags|=PAspect;
198 if(cwin->flags&CLIENTWIN_PROP_I_RSZINC){
199 cwin->size_hints.flags&=~PResizeInc;
200 }else if(cwin->flags&CLIENTWIN_PROP_RSZINC){
201 cwin->size_hints.width_inc=tmp.width_inc;
202 cwin->size_hints.height_inc=tmp.height_inc;
203 cwin->size_hints.flags|=PResizeInc;
208 void clientwin_get_set_name(WClientWin *cwin)
214 list=netwm_get_name(cwin);
217 list=xwindow_get_text_property(cwin->win, XA_WM_NAME, &n);
219 cwin->flags|=CLIENTWIN_USE_NET_WM_NAME;
223 /* Special condition kludge: property exists, but couldn't
224 * be converted to a string list.
226 clientwin_set_name(cwin, (n==-1 ? "???" : NULL));
228 clientwin_set_name(cwin, *list);
229 XFreeStringList(list);
234 /* Some standard winprops */
237 bool clientwin_get_switchto(const WClientWin *cwin)
241 if(ioncore_g.opmode==IONCORE_OPMODE_INIT)
244 if(extl_table_gets_b(cwin->proptab, "switchto", &b))
247 return ioncore_g.switchto_new;
251 int clientwin_get_transient_mode(const WClientWin *cwin)
254 int mode=TRANSIENT_MODE_NORMAL;
256 if(extl_table_gets_s(cwin->proptab, "transient_mode", &s)){
257 if(strcmp(s, "current")==0)
258 mode=TRANSIENT_MODE_CURRENT;
259 else if(strcmp(s, "off")==0)
260 mode=TRANSIENT_MODE_OFF;
270 /*{{{ Manage/create */
273 static void configure_cwin_bw(Window win, int bw)
276 ulong wcmask=CWBorderWidth;
279 XConfigureWindow(ioncore_g.dpy, win, wcmask, &wc);
283 static void set_sane_gravity(Window win)
285 XSetWindowAttributes attr;
287 attr.win_gravity=NorthWestGravity;
289 XChangeWindowAttributes(ioncore_g.dpy, win,
290 CWWinGravity, &attr);
294 static bool clientwin_init(WClientWin *cwin, WWindow *par, Window win,
295 XWindowAttributes *attr)
301 cwin->state=WithdrawnState;
307 fp.mode=REGION_FIT_EXACT;
309 /* The idiot who invented special server-supported window borders that
310 * are not accounted for in the window size should be "taken behind a
313 cwin->orig_bw=attr->border_width;
314 configure_cwin_bw(cwin->win, 0);
315 if(cwin->orig_bw!=0 && cwin->size_hints.flags&PWinGravity){
316 fp.g.x+=xgravity_deltax(cwin->size_hints.win_gravity,
317 -cwin->orig_bw, -cwin->orig_bw);
318 fp.g.y+=xgravity_deltay(cwin->size_hints.win_gravity,
319 -cwin->orig_bw, -cwin->orig_bw);
322 set_sane_gravity(cwin->win);
325 cwin->cmap=attr->colormap;
329 cwin->event_mask=IONCORE_EVENTMASK_CLIENTWIN;
331 region_init(&(cwin->region), par, &fp);
333 cwin->region.flags|=REGION_GRAB_ON_PARENT;
334 region_add_bindmap(&cwin->region, ioncore_clientwin_bindmap);
336 XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
338 clientwin_register(cwin);
339 clientwin_get_set_name(cwin);
340 clientwin_get_colormaps(cwin);
341 clientwin_get_protocols(cwin);
342 clientwin_get_winprops(cwin);
343 clientwin_get_size_hints(cwin);
345 XSaveContext(ioncore_g.dpy, win, ioncore_g.win_context, (XPointer)cwin);
346 XAddToSaveSet(ioncore_g.dpy, win);
352 static WClientWin *create_clientwin(WWindow *par, Window win,
353 XWindowAttributes *attr)
355 CREATEOBJ_IMPL(WClientWin, clientwin, (p, par, win, attr));
360 WClientWin *clientwin_get_transient_for(const WClientWin *cwin)
363 WClientWin *tfor=NULL;
365 if(clientwin_get_transient_mode(cwin)!=TRANSIENT_MODE_NORMAL)
368 if(!XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin))
374 tfor=XWINDOW_REGION_OF_T(tforwin, WClientWin);
377 warn(TR("The transient_for hint for \"%s\" points to itself."),
378 region_name((WRegion*)cwin));
379 }else if(tfor==NULL){
380 if(xwindow_region_of(tforwin)!=NULL){
381 warn(TR("Client window \"%s\" has broken transient_for hint. "
382 "(\"Extended WM hints\" multi-parent brain damage?)"),
383 region_name((WRegion*)cwin));
385 }else if(!region_same_rootwin((WRegion*)cwin, (WRegion*)tfor)){
386 warn(TR("The transient_for window for \"%s\" is not on the same "
387 "screen."), region_name((WRegion*)cwin));
396 static bool postmanage_check(WClientWin *cwin, XWindowAttributes *attr)
398 /* Check that the window exists. The previous check and selectinput
399 * do not seem to catch all cases of window destroyal.
401 XSync(ioncore_g.dpy, False);
403 if(XGetWindowAttributes(ioncore_g.dpy, cwin->win, attr))
406 warn(TR("Window %#x disappeared."), cwin->win);
412 static bool do_manage_mrsh(bool (*fn)(WClientWin *cwin, WManageParams *pm),
415 return fn((WClientWin*)p[0], (WManageParams*)p[1]);
420 static bool do_manage_mrsh_extl(ExtlFn fn, void **p)
422 WClientWin *cwin=(WClientWin*)p[0];
423 WManageParams *mp=(WManageParams*)p[1];
424 ExtlTab t=manageparams_to_table(mp);
427 extl_call(fn, "ot", "b", cwin, t, &ret);
431 return (ret && REGION_MANAGER(cwin)!=NULL);
435 /* This is called when a window is mapped on the root window.
436 * We want to check if we should manage the window and how and
439 WClientWin* ioncore_manage_clientwin(Window win, bool maprq)
442 WClientWin *cwin=NULL;
443 XWindowAttributes attr;
445 int init_state=NormalState;
446 WManageParams param=MANAGEPARAMS_INIT;
451 /* Is the window already being managed? */
452 cwin=XWINDOW_REGION_OF_T(win, WClientWin);
456 /* Select for UnmapNotify and DestroyNotify as the
457 * window might get destroyed or unmapped in the meanwhile.
459 xwindow_unmanaged_selectinput(win, StructureNotifyMask);
461 if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
463 warn(TR("Window %#x disappeared."), win);
469 hints=XGetWMHints(ioncore_g.dpy, win);
472 if(hints->flags&StateHint)
473 init_state=hints->initial_state;
475 if(!param.dockapp && init_state==WithdrawnState &&
476 hints->flags&IconWindowHint && hints->icon_window!=None){
477 Window icon_win=hints->icon_window;
478 XWindowAttributes icon_attr;
480 if(!XGetWindowAttributes(ioncore_g.dpy, icon_win, &icon_attr)){
482 warn(TR("Window %#x disappeared."), win);
488 if(attr.map_state==IsViewable){
489 /* The dockapp might be displaying its "main" window if no
490 * wm that understands dockapps has been managing it.
492 XUnmapWindow(ioncore_g.dpy, win);
495 /* Main window is unmapped on initial scan, but icon window
496 * is mapped. Let's hope it's a dockapp left by e.g. us.
498 if(icon_attr.map_state==IsViewable)
509 xwindow_unmanaged_selectinput(win, 0);
510 xwindow_unmanaged_selectinput(icon_win, StructureNotifyMask);
512 /* Copy WM_CLASS as _ION_DOCKAPP_HACK */
514 p=xwindow_get_text_property(win, XA_WM_CLASS, &n);
517 xwindow_set_text_property(icon_win, ioncore_g.atom_dockapp_hack,
518 (const char **)p, n);
521 const char *pdummy[2]={"unknowndockapp", "UnknownDockapp"};
522 xwindow_set_text_property(icon_win, ioncore_g.atom_dockapp_hack,
534 /* Do we really want to manage it? */
535 if(!param.dockapp && (attr.override_redirect ||
536 (!maprq && attr.map_state!=IsViewable))){
540 attr.width=maxof(attr.width, 1);
541 attr.height=maxof(attr.height, 1);
543 /* Find root window */
544 FOR_ALL_ROOTWINS(rootwin){
545 if(WROOTWIN_ROOT(rootwin)==attr.root)
550 warn(TR("Unable to find a matching root window!"));
554 /* Allocate and initialize */
555 cwin=create_clientwin((WWindow*)rootwin, win, &attr);
562 param.geom=REGION_GEOM(cwin);
564 param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
565 param.switchto=(init_state!=IconicState &&
566 (param.jumpto || clientwin_get_switchto(cwin)));
567 param.gravity=(cwin->size_hints.flags&PWinGravity
568 ? cwin->size_hints.win_gravity
570 param.tfor=clientwin_get_transient_for(cwin);
572 if(!extl_table_gets_b(cwin->proptab, "userpos", ¶m.userpos))
573 param.userpos=(cwin->size_hints.flags&USPosition);
575 if(cwin->flags&SIZEHINT_PROPS){
576 /* If size hints have been messed with, readjust requested geometry
577 * here. If programs themselves give incompatible geometries and
578 * things don't look good then, it's their fault.
580 region_size_hints_correct((WRegion*)cwin, ¶m.geom.w, ¶m.geom.h,
587 if(!hook_call_alt(clientwin_do_manage_alt, &mrshpm,
588 (WHookMarshall*)do_manage_mrsh,
589 (WHookMarshallExtl*)do_manage_mrsh_extl)){
590 warn(TR("Unable to manage client window %#x."), win);
594 if(ioncore_g.opmode==IONCORE_OPMODE_NORMAL &&
595 !region_is_fully_mapped((WRegion*)cwin) &&
596 !region_skip_focus((WRegion*)cwin)){
597 region_set_activity((WRegion*)cwin, SETPARAM_SET);
600 if(postmanage_check(cwin, &attr)){
601 /* Check for focus_next==NULL does not play nicely with
602 * pointer_focus_hack.
604 /*if(param.jumpto && ioncore_g.focus_next==NULL)*/
605 if(param.jumpto && !region_manager_is_focusnext((WRegion*)cwin))
606 region_goto((WRegion*)cwin);
607 hook_call_o(clientwin_mapped_hook, (Obj*)cwin);
612 clientwin_destroyed(cwin);
616 xwindow_unmanaged_selectinput(win, 0);
621 void clientwin_tfor_changed(WClientWin *cwin)
624 WManageParams param=MANAGEPARAMS_INIT;
625 bool succeeded=FALSE;
626 param.tfor=clientwin_get_transient_for(cwin);
630 region_rootpos((WRegion*)cwin, &(param.geom.x), &(param.geom.y));
631 param.geom.w=REGION_GEOM(cwin).w;
632 param.geom.h=REGION_GEOM(cwin).h;
635 param.switchto=region_may_control_focus((WRegion*)cwin);
636 param.jumpto=extl_table_is_bool_set(cwin->proptab, "jumpto");
637 param.gravity=ForgetGravity;
639 CALL_ALT_B(succeeded, clientwin_do_manage_alt, (cwin, ¶m));
640 warn("WM_TRANSIENT_FOR changed for \"%s\".",
641 region_name((WRegion*)cwin));
643 warn(TR("Changes is WM_TRANSIENT_FOR property are unsupported."));
651 /*{{{ Unmanage/destroy */
654 static bool reparent_root(WClientWin *cwin)
656 XWindowAttributes attr;
661 if(!XGetWindowAttributes(ioncore_g.dpy, cwin->win, &attr))
664 par=REGION_PARENT(cwin);
667 x=REGION_GEOM(cwin).x;
668 y=REGION_GEOM(cwin).y;
670 int dr=REGION_GEOM(par).w-REGION_GEOM(cwin).w-REGION_GEOM(cwin).x;
671 int db=REGION_GEOM(par).h-REGION_GEOM(cwin).h-REGION_GEOM(cwin).y;
675 XTranslateCoordinates(ioncore_g.dpy, par->win, attr.root, 0, 0,
678 x-=xgravity_deltax(cwin->size_hints.win_gravity,
679 maxof(0, REGION_GEOM(cwin).x), dr);
680 y-=xgravity_deltay(cwin->size_hints.win_gravity,
681 maxof(0, REGION_GEOM(cwin).y), db);
684 XReparentWindow(ioncore_g.dpy, cwin->win, attr.root, x, y);
690 void clientwin_deinit(WClientWin *cwin)
695 region_pointer_focus_hack(&cwin->region);
697 xwindow_unmanaged_selectinput(cwin->win, 0);
698 XUnmapWindow(ioncore_g.dpy, cwin->win);
701 configure_cwin_bw(cwin->win, cwin->orig_bw);
703 if(reparent_root(cwin)){
704 if(ioncore_g.opmode==IONCORE_OPMODE_DEINIT){
705 XMapWindow(ioncore_g.dpy, cwin->win);
706 /* Make sure the topmost window has focus; it doesn't really
707 * matter which one has as long as some has.
709 xwindow_do_set_focus(cwin->win);
711 set_clientwin_state(cwin, WithdrawnState);
712 netwm_delete_state(cwin);
716 XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
717 XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
720 clientwin_clear_colormaps(cwin);
722 region_deinit((WRegion*)cwin);
727 static bool mrsh_u_c(WHookDummy *fn, void *param)
733 static bool mrsh_u_extl(ExtlFn fn, void *param)
735 double d=*(Window*)param;
736 extl_call(fn, "d", NULL, d);
740 static void clientwin_do_unmapped(WClientWin *cwin, Window win)
742 cwin->flags|=CLIENTWIN_UNMAP_RQ;
744 /* First try a graceful chain-dispose */
745 if(!region_rqdispose((WRegion*)cwin)){
746 /* But force dispose anyway */
747 region_dispose((WRegion*)cwin);
750 hook_call(clientwin_unmapped_hook, &win, mrsh_u_c, mrsh_u_extl);
753 /* Used when the window was unmapped */
754 void clientwin_unmapped(WClientWin *cwin)
756 clientwin_do_unmapped(cwin, cwin->win);
760 /* Used when the window was deastroyed */
761 void clientwin_destroyed(WClientWin *cwin)
763 Window win=cwin->win;
764 XRemoveFromSaveSet(ioncore_g.dpy, cwin->win);
765 XDeleteContext(ioncore_g.dpy, cwin->win, ioncore_g.win_context);
766 xwindow_unmanaged_selectinput(cwin->win, 0);
768 clientwin_do_unmapped(cwin, win);
778 static bool send_clientmsg(Window win, Atom a, Time stmp)
780 XClientMessageEvent ev;
782 ev.type=ClientMessage;
784 ev.message_type=ioncore_g.atom_wm_protocols;
789 return (XSendEvent(ioncore_g.dpy, win, False, 0L, (XEvent*)&ev)!=0);
794 * Attempt to kill (with \code{XKillWindow}) the client that owns
795 * the X window correspoding to \var{cwin}.
798 void clientwin_kill(WClientWin *cwin)
800 XKillClient(ioncore_g.dpy, cwin->win);
804 void clientwin_rqclose(WClientWin *cwin, bool relocate_ignored)
806 /* Ignore relocate parameter -- client windows can always be
807 * destroyed by the application in any case, so way may just as
808 * well assume relocate is always set.
811 if(cwin->flags&CLIENTWIN_P_WM_DELETE){
812 send_clientmsg(cwin->win, ioncore_g.atom_wm_delete,
813 ioncore_get_timestamp());
815 warn(TR("Client does not support the WM_DELETE protocol."));
823 /*{{{ State (hide/show) */
826 static void set_clientwin_state(WClientWin *cwin, int state)
828 if(cwin->state!=state){
830 xwindow_set_state_property(cwin->win, state);
835 static void hide_clientwin(WClientWin *cwin)
837 region_pointer_focus_hack(&cwin->region);
839 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
840 XMoveWindow(ioncore_g.dpy, cwin->win,
841 -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h);
845 set_clientwin_state(cwin, IconicState);
846 XSelectInput(ioncore_g.dpy, cwin->win,
847 cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
848 XUnmapWindow(ioncore_g.dpy, cwin->win);
849 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
853 static void show_clientwin(WClientWin *cwin)
855 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC){
856 XMoveWindow(ioncore_g.dpy, cwin->win,
857 REGION_GEOM(cwin).x, REGION_GEOM(cwin).y);
858 if(cwin->state==NormalState)
862 XSelectInput(ioncore_g.dpy, cwin->win,
863 cwin->event_mask&~(StructureNotifyMask|EnterWindowMask));
864 XMapWindow(ioncore_g.dpy, cwin->win);
865 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
866 set_clientwin_state(cwin, NormalState);
873 /*{{{ Resize/reparent/reconf helpers */
876 void clientwin_notify_rootpos(WClientWin *cwin, int rootx, int rooty)
886 ce.xconfigure.type=ConfigureNotify;
887 ce.xconfigure.event=win;
888 ce.xconfigure.window=win;
889 ce.xconfigure.x=rootx-cwin->orig_bw;
890 ce.xconfigure.y=rooty-cwin->orig_bw;
891 ce.xconfigure.width=REGION_GEOM(cwin).w;
892 ce.xconfigure.height=REGION_GEOM(cwin).h;
893 ce.xconfigure.border_width=cwin->orig_bw;
894 ce.xconfigure.above=None;
895 ce.xconfigure.override_redirect=False;
897 XSelectInput(ioncore_g.dpy, win, cwin->event_mask&~StructureNotifyMask);
898 XSendEvent(ioncore_g.dpy, win, False, StructureNotifyMask, &ce);
899 XSelectInput(ioncore_g.dpy, win, cwin->event_mask);
903 static void sendconfig_clientwin(WClientWin *cwin)
907 region_rootpos(&cwin->region, &rootx, &rooty);
908 clientwin_notify_rootpos(cwin, rootx, rooty);
912 static void do_reparent_clientwin(WClientWin *cwin, Window win, int x, int y)
914 XSelectInput(ioncore_g.dpy, cwin->win,
915 cwin->event_mask&~StructureNotifyMask);
916 XReparentWindow(ioncore_g.dpy, cwin->win, win, x, y);
917 XSelectInput(ioncore_g.dpy, cwin->win, cwin->event_mask);
921 static void convert_geom(const WFitParams *fp,
922 WClientWin *cwin, WRectangle *geom)
924 WFitParams fptmp=*fp;
925 WSizePolicy szplcy=SIZEPOLICY_FULL_EXACT;
927 /*if(cwin->szplcy!=SIZEPOLICY_DEFAULT)
928 szplcy=cwin->szplcy;*/
930 sizepolicy(&szplcy, (WRegion*)cwin, NULL, REGION_RQGEOM_WEAK_ALL, &fptmp);
939 /*{{{ Region dynfuns */
942 static bool clientwin_fitrep(WClientWin *cwin, WWindow *np,
943 const WFitParams *fp)
949 if(np!=NULL && !region_same_rootwin((WRegion*)cwin, (WRegion*)np))
952 if(fp->mode®ION_FIT_WHATEVER){
955 geom.w=REGION_GEOM(cwin).w;
956 geom.h=REGION_GEOM(cwin).h;
961 changes=(REGION_GEOM(cwin).x!=geom.x ||
962 REGION_GEOM(cwin).y!=geom.y ||
963 REGION_GEOM(cwin).w!=geom.w ||
964 REGION_GEOM(cwin).h!=geom.h);
966 REGION_GEOM(cwin)=geom;
968 if(np==NULL && !changes)
972 region_unset_parent((WRegion*)cwin);
973 do_reparent_clientwin(cwin, np->win, geom.x, geom.y);
974 region_set_parent((WRegion*)cwin, np);
975 sendconfig_clientwin(cwin);
977 if(!REGION_IS_FULLSCREEN(cwin))
978 cwin->flags&=~CLIENTWIN_FS_RQ;
980 netwm_update_state(cwin);
986 if(cwin->flags&CLIENTWIN_PROP_ACROBATIC && !REGION_IS_MAPPED(cwin)){
987 XMoveResizeWindow(ioncore_g.dpy, cwin->win,
988 -2*REGION_GEOM(cwin).w, -2*REGION_GEOM(cwin).h,
991 XMoveResizeWindow(ioncore_g.dpy, cwin->win, geom.x, geom.y, w, h);
994 cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1000 static void clientwin_map(WClientWin *cwin)
1002 show_clientwin(cwin);
1003 REGION_MARK_MAPPED(cwin);
1007 static void clientwin_unmap(WClientWin *cwin)
1009 hide_clientwin(cwin);
1010 REGION_MARK_UNMAPPED(cwin);
1014 static void clientwin_do_set_focus(WClientWin *cwin, bool warp)
1016 if(cwin->flags&CLIENTWIN_P_WM_TAKE_FOCUS){
1017 Time stmp=ioncore_get_timestamp();
1018 send_clientmsg(cwin->win, ioncore_g.atom_wm_take_focus, stmp);
1021 region_finalise_focusing((WRegion*)cwin, cwin->win, warp);
1023 XSync(ioncore_g.dpy, 0);
1027 void clientwin_restack(WClientWin *cwin, Window other, int mode)
1029 xwindow_restack(cwin->win, other, mode);
1033 void clientwin_stacking(WClientWin *cwin, Window *bottomret, Window *topret)
1035 *bottomret=cwin->win;
1040 static Window clientwin_x_window(WClientWin *cwin)
1046 static void clientwin_activated(WClientWin *cwin)
1048 clientwin_install_colormap(cwin);
1052 static void clientwin_size_hints(WClientWin *cwin, WSizeHints *hints_ret)
1054 if(cwin->flags&CLIENTWIN_FS_RQ){
1055 /* Do not use size hints, when full screen mode has been
1056 * requested by the client window itself.
1058 sizehints_clear(hints_ret);
1060 xsizehints_to_sizehints(&cwin->size_hints, hints_ret);
1065 static int clientwin_orientation(WClientWin *cwin)
1067 return (cwin->flags&CLIENTWIN_PROP_O_VERT
1068 ? REGION_ORIENTATION_VERTICAL
1069 : (cwin->flags&CLIENTWIN_PROP_O_HORIZ
1070 ? REGION_ORIENTATION_HORIZONTAL
1071 : REGION_ORIENTATION_NONE));
1078 /*{{{ Identity & lookup */
1082 * Returns a table containing the properties \code{WM_CLASS} (table entries
1083 * \var{instance} and \var{class}) and \code{WM_WINDOW_ROLE} (\var{role})
1084 * properties for \var{cwin}. If a property is not set, the corresponding
1085 * field(s) are unset in the table.
1089 ExtlTab clientwin_get_ident(WClientWin *cwin)
1091 char **p=NULL, **p2=NULL, *wrole=NULL;
1092 int n=0, n2=0, n3=0, tmp=0;
1093 Window tforwin=None;
1095 bool dockapp_hack=FALSE;
1097 p=xwindow_get_text_property(cwin->win, XA_WM_CLASS, &n);
1099 p2=xwindow_get_text_property(cwin->win, ioncore_g.atom_dockapp_hack, &n2);
1101 dockapp_hack=(n2>0);
1104 /* Some dockapps do actually have WM_CLASS, so use it. */
1110 wrole=xwindow_get_string_property(cwin->win, ioncore_g.atom_wm_window_role,
1113 tab=extl_create_table();
1114 if(n>=2 && p[1]!=NULL)
1115 extl_table_sets_s(tab, "class", p[1]);
1116 if(n>=1 && p[0]!=NULL)
1117 extl_table_sets_s(tab, "instance", p[0]);
1119 extl_table_sets_s(tab, "role", wrole);
1121 if(XGetTransientForHint(ioncore_g.dpy, cwin->win, &tforwin)
1123 extl_table_sets_b(tab, "is_transient", TRUE);
1127 extl_table_sets_b(tab, "is_dockapp", TRUE);
1132 XFreeStringList(p2);
1143 /*{{{ ConfigureRequest */
1146 static bool check_fs_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1148 /* check full screen request */
1149 if((ev->value_mask&(CWWidth|CWHeight))==(CWWidth|CWHeight)){
1150 WRegion *grp=region_groupleader_of((WRegion*)cwin);
1151 WScreen *scr=clientwin_fullscreen_chkrq(cwin, ev->width, ev->height);
1153 if(scr!=NULL && REGION_MANAGER(grp)!=(WRegion*)scr){
1154 bool sw=clientwin_fullscreen_may_switchto(cwin);
1156 cwin->flags|=CLIENTWIN_FS_RQ;
1158 if(!region_fullscreen_scr(grp, scr, sw))
1159 cwin->flags&=~CLIENTWIN_FS_RQ;
1169 static bool check_normal_cfgrq(WClientWin *cwin, XConfigureRequestEvent *ev)
1171 if(ev->value_mask&(CWX|CWY|CWWidth|CWHeight)){
1172 WRQGeomParams rq=RQGEOMPARAMS_INIT;
1175 rq.flags=REGION_RQGEOM_WEAK_ALL|REGION_RQGEOM_ABSOLUTE;
1177 if(cwin->size_hints.flags&PWinGravity){
1178 rq.flags|=REGION_RQGEOM_GRAVITY;
1179 rq.gravity=cwin->size_hints.win_gravity;
1182 /* Do I need to insert another disparaging comment on the person who
1183 * invented special server-supported window borders that are not
1184 * accounted for in the window size? Keep it simple, stupid!
1186 if(cwin->size_hints.flags&PWinGravity){
1187 gdx=xgravity_deltax(cwin->size_hints.win_gravity,
1188 -cwin->orig_bw, -cwin->orig_bw);
1189 gdy=xgravity_deltay(cwin->size_hints.win_gravity,
1190 -cwin->orig_bw, -cwin->orig_bw);
1193 region_rootpos((WRegion*)cwin, &(rq.geom.x), &(rq.geom.y));
1194 rq.geom.w=REGION_GEOM(cwin).w;
1195 rq.geom.h=REGION_GEOM(cwin).h;
1197 if(ev->value_mask&CWWidth){
1198 /* If x was not changed, keep reference point where it was */
1199 if(cwin->size_hints.flags&PWinGravity){
1200 rq.geom.x+=xgravity_deltax(cwin->size_hints.win_gravity, 0,
1201 ev->width-rq.geom.w);
1203 rq.geom.w=maxof(ev->width, 1);
1204 rq.flags&=~REGION_RQGEOM_WEAK_W;
1206 if(ev->value_mask&CWHeight){
1207 /* If y was not changed, keep reference point where it was */
1208 if(cwin->size_hints.flags&PWinGravity){
1209 rq.geom.y+=xgravity_deltay(cwin->size_hints.win_gravity, 0,
1210 ev->height-rq.geom.h);
1212 rq.geom.h=maxof(ev->height, 1);
1213 rq.flags&=~REGION_RQGEOM_WEAK_H;
1215 if(ev->value_mask&CWX){
1216 rq.geom.x=ev->x+gdx;
1217 rq.flags&=~REGION_RQGEOM_WEAK_X;
1219 if(ev->value_mask&CWY){
1220 rq.geom.y=ev->y+gdy;
1221 rq.flags&=~REGION_RQGEOM_WEAK_Y;
1224 region_rqgeom((WRegion*)cwin, &rq, NULL);
1233 void clientwin_handle_configure_request(WClientWin *cwin,
1234 XConfigureRequestEvent *ev)
1236 if(ev->value_mask&CWBorderWidth)
1237 cwin->orig_bw=ev->border_width;
1239 cwin->flags|=CLIENTWIN_NEED_CFGNTFY;
1241 if(!(cwin->flags&CLIENTWIN_PROP_IGNORE_CFGRQ)){
1242 if(!check_fs_cfgrq(cwin, ev))
1243 check_normal_cfgrq(cwin, ev);
1246 if(cwin->flags&CLIENTWIN_NEED_CFGNTFY){
1247 sendconfig_clientwin(cwin);
1248 cwin->flags&=~CLIENTWIN_NEED_CFGNTFY;
1260 * Attempts to fix window size problems with non-ICCCM compliant
1264 void clientwin_nudge(WClientWin *cwin)
1266 XResizeWindow(ioncore_g.dpy, cwin->win,
1267 2*REGION_GEOM(cwin).w, 2*REGION_GEOM(cwin).h);
1268 XFlush(ioncore_g.dpy);
1269 XResizeWindow(ioncore_g.dpy, cwin->win,
1270 REGION_GEOM(cwin).w, REGION_GEOM(cwin).h);
1281 * Return the X window id for the client window.
1285 double clientwin_xid(WClientWin *cwin)
1297 static int last_checkcode=1;
1300 static ExtlTab clientwin_get_configuration(WClientWin *cwin)
1304 SMCfgCallback *cfg_cb;
1305 SMAddCallback *add_cb;
1307 tab=region_get_base_configuration((WRegion*)cwin);
1309 extl_table_sets_d(tab, "windowid", (double)(cwin->win));
1311 if(last_checkcode!=0){
1312 chkc=last_checkcode++;
1313 xwindow_set_integer_property(cwin->win, ioncore_g.atom_checkcode,
1315 extl_table_sets_i(tab, "checkcode", chkc);
1318 ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1327 static void do_sm(ExtlTab tab)
1329 SMAddCallback *add_cb;
1330 SMCfgCallback *cfg_cb;
1333 ioncore_get_sm_callbacks(&add_cb, &cfg_cb);
1336 ph=ioncore_get_load_pholder();
1339 if(!add_cb(ph, tab))
1340 destroy_obj((Obj*)ph);
1346 WRegion *clientwin_load(WWindow *par, const WFitParams *fp, ExtlTab tab)
1350 int chkc=0, real_chkc=0;
1351 WClientWin *cwin=NULL;
1352 XWindowAttributes attr;
1354 bool got_chkc=FALSE;
1356 if(!extl_table_gets_d(tab, "windowid", &wind) ||
1357 !extl_table_gets_i(tab, "checkcode", &chkc)){
1363 if(XWINDOW_REGION_OF(win)!=NULL){
1364 warn("Client window %x already managed.", win);
1368 got_chkc=xwindow_get_integer_property(win, ioncore_g.atom_checkcode,
1371 if(!got_chkc || real_chkc!=chkc){
1378 if(!XGetWindowAttributes(ioncore_g.dpy, win, &attr)){
1379 warn(TR("Window %#x disappeared."), win);
1383 if(attr.root!=region_root_of((WRegion*)par))
1386 if(attr.override_redirect ||
1387 (ioncore_g.opmode==IONCORE_OPMODE_INIT && attr.map_state!=IsViewable)){
1388 warn(TR("Saved client window does not want to be managed."));
1392 cwin=create_clientwin(par, win, &attr);
1397 /* Reparent and resize taking limits set by size hints into account */
1398 convert_geom(fp, cwin, &rg);
1399 REGION_GEOM(cwin)=rg;
1400 do_reparent_clientwin(cwin, par->win, rg.x, rg.y);
1401 XResizeWindow(ioncore_g.dpy, win, maxof(1, rg.w), maxof(1, rg.h));
1403 if(!postmanage_check(cwin, &attr)){
1404 clientwin_destroyed(cwin);
1408 return (WRegion*)cwin;
1415 /*{{{ Dynfuntab and class info */
1418 static DynFunTab clientwin_dynfuntab[]={
1419 {(DynFun*)region_fitrep,
1420 (DynFun*)clientwin_fitrep},
1428 {region_do_set_focus,
1429 clientwin_do_set_focus},
1431 {region_notify_rootpos,
1432 clientwin_notify_rootpos},
1438 clientwin_stacking},
1440 {(DynFun*)region_xwindow,
1441 (DynFun*)clientwin_x_window},
1444 clientwin_activated},
1447 clientwin_size_hints},
1449 {(DynFun*)region_orientation,
1450 (DynFun*)clientwin_orientation},
1452 {(DynFun*)region_rqclose,
1453 (DynFun*)clientwin_rqclose},
1455 {(DynFun*)region_get_configuration,
1456 (DynFun*)clientwin_get_configuration},
1463 IMPLCLASS(WClientWin, WRegion, clientwin_deinit, clientwin_dynfuntab);